Skip to content
2 changes: 1 addition & 1 deletion Jint/Collections/HybridDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal sealed class HybridDictionary<TValue> : IEngineDictionary<Key, TValue>,
{
private const int CutoverPoint = 9;
private const int InitialDictionarySize = 13;
private const int FixedSizeCutoverPoint = 6;
private const int FixedSizeCutoverPoint = 9;

private readonly bool _checkExistingKeys;
private ListDictionary<TValue> _list;
Expand Down
2 changes: 1 addition & 1 deletion Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool)

if (baseValue.IsObject())
{
var baseObj = Runtime.TypeConverter.ToObject(Realm, baseValue);
var baseObj = (ObjectInstance) baseValue;

if (reference.IsPrivateReference)
{
Expand Down
8 changes: 5 additions & 3 deletions Jint/Native/Array/ArrayConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -665,9 +665,11 @@ public override ObjectInstance Construct(JsCallArguments arguments, JsValue newT
newTarget = this;
}

var proto = _realm.Intrinsics.Function.GetPrototypeFromConstructor(
newTarget,
static intrinsics => intrinsics.Array.PrototypeObject);
var proto = ReferenceEquals(newTarget, this)
? PrototypeObject
: _realm.Intrinsics.Function.GetPrototypeFromConstructor(
newTarget,
static intrinsics => intrinsics.Array.PrototypeObject);

// check if we can figure out good size
var capacity = arguments.Length > 0 ? (ulong) arguments.Length : 0;
Expand Down
11 changes: 10 additions & 1 deletion Jint/Native/Date/DateConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,20 @@ public override ObjectInstance Construct(JsCallArguments arguments, JsValue newT
// fast path is building default, new Date()
if (arguments.Length == 0 || newTarget.IsUndefined())
{
var now = (_timeSystem.GetUtcNow().DateTime - Epoch).TotalMilliseconds;

// when newTarget is the built-in Date constructor, skip OrdinaryCreateFromConstructor
// to avoid the GetPrototypeFromConstructor property lookup
if (ReferenceEquals(newTarget, this))
{
return new JsDate(_engine, now);
}

return OrdinaryCreateFromConstructor(
newTarget,
static intrinsics => intrinsics.Date.PrototypeObject,
static (engine, _, dateValue) => new JsDate(engine, dateValue),
(_timeSystem.GetUtcNow().DateTime - Epoch).TotalMilliseconds);
now);
}

return ConstructUnlikely(arguments, newTarget);
Expand Down
21 changes: 17 additions & 4 deletions Jint/Native/Function/ScriptFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,23 @@ ObjectInstance IConstructor.Construct(JsCallArguments arguments, JsValue newTarg

if (kind == ConstructorKind.Base)
{
thisArgument = OrdinaryCreateFromConstructor(
newTarget,
static intrinsics => intrinsics.Object.PrototypeObject,
static (Engine engine, Realm _, object? _) => new JsObject(engine));
if (ReferenceEquals(newTarget, this)
&& _prototypeDescriptor is { } prototypeDescriptor
&& !prototypeDescriptor.IsAccessorDescriptor())
{
var prototype = prototypeDescriptor.Value as ObjectInstance ?? _realm.Intrinsics.Object.PrototypeObject;
thisArgument = new JsObject(_engine)
{
_prototype = prototype
};
}
else
{
thisArgument = OrdinaryCreateFromConstructor(
newTarget,
static intrinsics => intrinsics.Object.PrototypeObject,
static (Engine engine, Realm _, object? _) => new JsObject(engine));
}
}

ref readonly var calleeContext = ref PrepareForOrdinaryCall(newTarget);
Expand Down
8 changes: 8 additions & 0 deletions Jint/Native/Map/MapConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ private JsMap ConstructMap(JsValue newTarget)
Throw.TypeError(_realm);
}

if (ReferenceEquals(newTarget, this))
{
return new JsMap(_engine, _realm)
{
_prototype = PrototypeObject
};
}

var map = OrdinaryCreateFromConstructor(
newTarget,
static intrinsics => intrinsics.Map.PrototypeObject,
Expand Down
19 changes: 15 additions & 4 deletions Jint/Native/Promise/PromiseConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,21 @@ public override ObjectInstance Construct(JsCallArguments arguments, JsValue newT
return null;
}

var promise = OrdinaryCreateFromConstructor(
newTarget,
static intrinsics => intrinsics.Promise.PrototypeObject,
static (Engine engine, Realm _, object? _) => new JsPromise(engine));
JsPromise promise;
if (ReferenceEquals(newTarget, this))
{
promise = new JsPromise(_engine)
{
_prototype = PrototypeObject
};
}
else
{
promise = OrdinaryCreateFromConstructor(
newTarget,
static intrinsics => intrinsics.Promise.PrototypeObject,
static (Engine engine, Realm _, object? _) => new JsPromise(engine));
}

var (resolve, reject) = promise.CreateResolvingFunctions();
try
Expand Down
8 changes: 8 additions & 0 deletions Jint/Native/RegExp/RegExpConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,14 @@ private JsRegExp RegExpInitialize(JsRegExp r, JsValue pattern, JsValue flags)

private JsRegExp RegExpAlloc(JsValue newTarget)
{
if (ReferenceEquals(newTarget, this))
{
return new JsRegExp(_engine)
{
_prototype = PrototypeObject
};
}

var r = OrdinaryCreateFromConstructor(
newTarget,
static intrinsics => intrinsics.RegExp.PrototypeObject,
Expand Down
8 changes: 8 additions & 0 deletions Jint/Native/Set/SetConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ private JsSet ConstructSet(JsValue newTarget)
Throw.TypeError(_engine.Realm);
}

if (ReferenceEquals(newTarget, this))
{
return new JsSet(_engine)
{
_prototype = PrototypeObject
};
}

var set = OrdinaryCreateFromConstructor(
newTarget,
static intrinsics => intrinsics.Set.PrototypeObject,
Expand Down
51 changes: 45 additions & 6 deletions Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Jint.Runtime.Interpreter.Expressions;
internal sealed class JintIdentifierExpression : JintExpression
{
private readonly Environment.BindingName _identifier;
private Environment? _cachedEnvironment;
private bool _cachedStrict;

public JintIdentifierExpression(Identifier expression) : this(expression, new Environment.BindingName(expression.Name))
{
Expand Down Expand Up @@ -37,12 +39,29 @@ protected override object EvaluateInternal(EvaluationContext context)
var env = engine.ExecutionContext.LexicalEnvironment;
var strict = StrictModeScope.IsStrictModeCode;

if (ReferenceEquals(env, _cachedEnvironment)
&& _cachedStrict == strict
&& env.HasBinding(_identifier))
{
return engine._referencePool.Rent(env, _identifier.Value, strict, thisValue: null);
}

if (!JintEnvironment.TryGetIdentifierEnvironmentWithBinding(env, _identifier, out var identifierEnvironment))
{
// Binding not found - create unresolvable reference
return engine._referencePool.Rent(Reference.Unresolvable, _identifier.Value, strict, thisValue: null);
}

if (ReferenceEquals(identifierEnvironment, env))
{
_cachedEnvironment = env;
_cachedStrict = strict;
}
else
{
_cachedEnvironment = null;
}

return engine._referencePool.Rent(identifierEnvironment, _identifier.Value, strict, thisValue: null);
}

Expand All @@ -60,14 +79,34 @@ public override JsValue GetValue(EvaluationContext context)
var engine = context.Engine;
var env = engine.ExecutionContext.LexicalEnvironment;
var strict = StrictModeScope.IsStrictModeCode;
JsValue? value;

if (JintEnvironment.TryGetIdentifierEnvironmentWithBindingValue(
env,
identifier,
strict,
out _,
out var value))
if (ReferenceEquals(env, _cachedEnvironment)
&& _cachedStrict == strict
&& env.TryGetBinding(identifier, strict, out value))
{
if (value is null)
{
ThrowNotInitialized(engine);
}
}
else if (JintEnvironment.TryGetIdentifierEnvironmentWithBindingValue(
env,
identifier,
strict,
out var identifierEnvironment,
out value))
{
if (ReferenceEquals(identifierEnvironment, env))
{
_cachedEnvironment = env;
_cachedStrict = strict;
}
else
{
_cachedEnvironment = null;
}

if (value is null)
{
ThrowNotInitialized(engine);
Expand Down
58 changes: 57 additions & 1 deletion Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Jint.Native;
using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Environments;

namespace Jint.Runtime.Interpreter.Expressions;
Expand All @@ -13,6 +16,8 @@ internal sealed class JintMemberExpression : JintExpression
private JintExpression? _propertyExpression;
private JsValue? _determinedProperty;
private bool _initialized;
private ObjectInstance? _cachedReadObject;
private PropertyDescriptor? _cachedReadDescriptor;

private static readonly JsValue _nullMarker = new JsString("NULL MARKER");

Expand All @@ -39,7 +44,7 @@ internal static JsValue InitializeDeterminedProperty(MemberExpression expression
return property ?? _nullMarker;
}

protected override object EvaluateInternal(EvaluationContext context)
private void EnsureInitialized()
{
if (!_initialized)
{
Expand All @@ -55,6 +60,11 @@ protected override object EvaluateInternal(EvaluationContext context)

_initialized = true;
}
}

protected override object EvaluateInternal(EvaluationContext context)
{
EnsureInitialized();

JsValue? actualThis = null;
object? baseReferenceName = null;
Expand Down Expand Up @@ -135,6 +145,52 @@ private static Reference MakePrivateReference(Engine engine, JsValue baseValue,
/// </summary>
public override JsValue GetValue(EvaluationContext context)
{
EnsureInitialized();

// Fast path for common property reads (e.g. obj.prop) where we can avoid creating and resolving a Reference.
if (_propertyExpression is null
&& _determinedProperty is JsString determinedProperty
&& !_memberExpression.Optional
&& !_objectExpression._expression.IsOptional()
&& _objectExpression is not JintSuperExpression)
{
var baseValue = _objectExpression.GetValue(context);
if (baseValue is ObjectInstance baseObject)
{
context.LastSyntaxElement = _expression;

if ((baseObject._type & InternalTypes.PlainObject) != InternalTypes.Empty)
{
if (ReferenceEquals(baseObject, _cachedReadObject)
&& _cachedReadDescriptor is not null)
{
return ObjectInstance.UnwrapJsValue(_cachedReadDescriptor, baseObject);
}

var ownDescriptor = baseObject.GetOwnProperty(determinedProperty);
if (!ReferenceEquals(ownDescriptor, PropertyDescriptor.Undefined))
{
if (!ownDescriptor.Configurable)
{
_cachedReadObject = baseObject;
_cachedReadDescriptor = ownDescriptor;
}
else
{
_cachedReadObject = null;
_cachedReadDescriptor = null;
}

return ObjectInstance.UnwrapJsValue(ownDescriptor, baseObject);
}
}

_cachedReadObject = null;
_cachedReadDescriptor = null;
return baseObject.Get(determinedProperty, baseObject);
}
}

var result = Evaluate(context);
if (result is not Reference reference)
{
Expand Down
7 changes: 7 additions & 0 deletions Jint/Runtime/TypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ public static JsValue ToNumeric(JsValue value)
return value;
}

// fast path for Date objects - avoid expensive ToPrimitive chain
// (Symbol.toPrimitive lookup → exotic call → OrdinaryToPrimitive → valueOf)
if (value is JsDate jsDate)
{
return jsDate._dateValue.ToJsValue();
}

var primValue = ToPrimitive(value, Types.Number);
if (primValue.IsBigInt())
{
Expand Down