ExpressionCache.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. using System.Runtime.CompilerServices;
  2. using Jint.Native;
  3. using Jint.Native.Iterator;
  4. namespace Jint.Runtime.Interpreter.Expressions;
  5. /// <summary>
  6. /// Optimizes constants values from expression array and only returns the actual JsValue in consecutive calls.
  7. /// </summary>
  8. internal sealed class ExpressionCache
  9. {
  10. private object?[] _expressions = [];
  11. private bool _fullyCached;
  12. internal bool HasSpreads { get; private set; }
  13. internal void Initialize(EvaluationContext context, ReadOnlySpan<Expression> arguments)
  14. {
  15. if (arguments.Length == 0)
  16. {
  17. _fullyCached = true;
  18. _expressions = [];
  19. return;
  20. }
  21. _expressions = new object?[arguments.Length];
  22. _fullyCached = true;
  23. for (var i = 0; i < (uint) arguments.Length; i++)
  24. {
  25. var argument = arguments[i];
  26. if (argument is null)
  27. {
  28. _fullyCached = false;
  29. continue;
  30. }
  31. var expression = JintExpression.Build(argument);
  32. if (argument.Type == NodeType.Literal)
  33. {
  34. _expressions[i] = expression.GetValue(context).Clone();
  35. continue;
  36. }
  37. _expressions[i] = expression;
  38. _fullyCached &= argument.Type == NodeType.Literal;
  39. HasSpreads |= CanSpread(argument);
  40. if (argument.Type == NodeType.ArrayExpression)
  41. {
  42. ref readonly var elements = ref ((ArrayExpression) argument).Elements;
  43. foreach (var e in elements.AsSpan())
  44. {
  45. HasSpreads |= CanSpread(e);
  46. }
  47. }
  48. }
  49. }
  50. public JsValue[] ArgumentListEvaluation(EvaluationContext context, out bool rented)
  51. {
  52. rented = false;
  53. if (_fullyCached)
  54. {
  55. return Unsafe.As<JsValue[]>(_expressions);
  56. }
  57. if (HasSpreads)
  58. {
  59. var args = new List<JsValue>(_expressions.Length);
  60. BuildArgumentsWithSpreads(context, args);
  61. return args.ToArray();
  62. }
  63. var arguments = context.Engine._jsValueArrayPool.RentArray(_expressions.Length);
  64. rented = true;
  65. BuildArguments(context, arguments);
  66. return arguments;
  67. }
  68. internal void BuildArguments(EvaluationContext context, JsValue[] targetArray)
  69. {
  70. var expressions = _expressions;
  71. for (uint i = 0; i < (uint) expressions.Length; i++)
  72. {
  73. targetArray[i] = GetValue(context, expressions[i])!;
  74. }
  75. }
  76. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  77. public JsValue GetValue(EvaluationContext context, int index)
  78. {
  79. return GetValue(context, _expressions[index]);
  80. }
  81. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  82. private static JsValue GetValue(EvaluationContext context, object? value)
  83. {
  84. return value switch
  85. {
  86. JintExpression expression => expression.GetValue(context).Clone(),
  87. _ => (JsValue) value!,
  88. };
  89. }
  90. public bool IsAnonymousFunctionDefinition(int index)
  91. {
  92. var expressions = _expressions;
  93. return index < expressions.Length && (expressions[index] as JintExpression)?._expression.IsAnonymousFunctionDefinition() == true;
  94. }
  95. private static bool CanSpread(Node? e)
  96. {
  97. if (e is null)
  98. {
  99. return false;
  100. }
  101. return e.Type == NodeType.SpreadElement || e is AssignmentExpression { Right.Type: NodeType.SpreadElement };
  102. }
  103. internal JsValue[] DefaultSuperCallArgumentListEvaluation(EvaluationContext context)
  104. {
  105. // This branch behaves similarly to constructor(...args) { super(...args); }.
  106. // The most notable distinction is that while the aforementioned ECMAScript source text observably calls
  107. // the @@iterator method on %Array.prototype%, this function does not.
  108. var spreadExpression = (JintSpreadExpression) _expressions[0]!;
  109. var array = (JsArray) spreadExpression._argument.GetValue(context);
  110. var length = array.GetLength();
  111. var args = new List<JsValue>((int) length);
  112. for (uint j = 0; j < length; ++j)
  113. {
  114. array.TryGetValue(j, out var value);
  115. args.Add(value);
  116. }
  117. return args.ToArray();
  118. }
  119. internal void BuildArgumentsWithSpreads(EvaluationContext context, List<JsValue> target)
  120. {
  121. foreach (var expression in _expressions)
  122. {
  123. if (expression is JintSpreadExpression jse)
  124. {
  125. jse.GetValueAndCheckIterator(context, out var objectInstance, out var iterator);
  126. // optimize for array unless someone has touched the iterator
  127. if (objectInstance is JsArray { HasOriginalIterator: true } ai)
  128. {
  129. var length = ai.GetLength();
  130. for (uint j = 0; j < length; ++j)
  131. {
  132. ai.TryGetValue(j, out var value);
  133. target.Add(value);
  134. }
  135. }
  136. else
  137. {
  138. var protocol = new ArraySpreadProtocol(context.Engine, target, iterator!);
  139. protocol.Execute();
  140. }
  141. }
  142. else
  143. {
  144. target.Add(GetValue(context, expression)!);
  145. }
  146. }
  147. }
  148. private sealed class ArraySpreadProtocol : IteratorProtocol
  149. {
  150. private readonly List<JsValue> _instance;
  151. public ArraySpreadProtocol(
  152. Engine engine,
  153. List<JsValue> instance,
  154. IteratorInstance iterator) : base(engine, iterator, 0)
  155. {
  156. _instance = instance;
  157. }
  158. protected override void ProcessItem(JsValue[] arguments, JsValue currentValue)
  159. {
  160. _instance.Add(currentValue);
  161. }
  162. }
  163. }