diff --git a/Jint/Collections/HybridDictionary.cs b/Jint/Collections/HybridDictionary.cs index 10c67b0586..709293fcc4 100644 --- a/Jint/Collections/HybridDictionary.cs +++ b/Jint/Collections/HybridDictionary.cs @@ -9,7 +9,7 @@ internal sealed class HybridDictionary : IEngineDictionary, { 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 _list; diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 8af8a707e8..ccba86ac48 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -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) { diff --git a/Jint/Native/Array/ArrayConstructor.cs b/Jint/Native/Array/ArrayConstructor.cs index 17beb2e7b4..7d19526bf9 100644 --- a/Jint/Native/Array/ArrayConstructor.cs +++ b/Jint/Native/Array/ArrayConstructor.cs @@ -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; diff --git a/Jint/Native/Date/DateConstructor.cs b/Jint/Native/Date/DateConstructor.cs index d73039994d..88ba7cf4ee 100644 --- a/Jint/Native/Date/DateConstructor.cs +++ b/Jint/Native/Date/DateConstructor.cs @@ -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); diff --git a/Jint/Native/Function/ScriptFunction.cs b/Jint/Native/Function/ScriptFunction.cs index 4b07bf62a8..511fce24f6 100644 --- a/Jint/Native/Function/ScriptFunction.cs +++ b/Jint/Native/Function/ScriptFunction.cs @@ -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); diff --git a/Jint/Native/Map/MapConstructor.cs b/Jint/Native/Map/MapConstructor.cs index e7f3dc2525..91c8affb7e 100644 --- a/Jint/Native/Map/MapConstructor.cs +++ b/Jint/Native/Map/MapConstructor.cs @@ -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, diff --git a/Jint/Native/Promise/PromiseConstructor.cs b/Jint/Native/Promise/PromiseConstructor.cs index b7ad335532..bebb47b390 100644 --- a/Jint/Native/Promise/PromiseConstructor.cs +++ b/Jint/Native/Promise/PromiseConstructor.cs @@ -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 diff --git a/Jint/Native/RegExp/RegExpConstructor.cs b/Jint/Native/RegExp/RegExpConstructor.cs index ab19e8835e..da8691dfd1 100644 --- a/Jint/Native/RegExp/RegExpConstructor.cs +++ b/Jint/Native/RegExp/RegExpConstructor.cs @@ -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, diff --git a/Jint/Native/Set/SetConstructor.cs b/Jint/Native/Set/SetConstructor.cs index dc79da8914..cb3383c457 100644 --- a/Jint/Native/Set/SetConstructor.cs +++ b/Jint/Native/Set/SetConstructor.cs @@ -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, diff --git a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs index e420cdb5d8..3b7d0ebca2 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs @@ -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)) { @@ -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); } @@ -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); diff --git a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs index 495dbdc0fe..7c745d4197 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs @@ -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; @@ -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"); @@ -39,7 +44,7 @@ internal static JsValue InitializeDeterminedProperty(MemberExpression expression return property ?? _nullMarker; } - protected override object EvaluateInternal(EvaluationContext context) + private void EnsureInitialized() { if (!_initialized) { @@ -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; @@ -135,6 +145,52 @@ private static Reference MakePrivateReference(Engine engine, JsValue baseValue, /// 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) { diff --git a/Jint/Runtime/TypeConverter.cs b/Jint/Runtime/TypeConverter.cs index d2da862abd..c1c32a82c8 100644 --- a/Jint/Runtime/TypeConverter.cs +++ b/Jint/Runtime/TypeConverter.cs @@ -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()) {