Forráskód Böngészése

Improve global object setup and access performance (#1280)

Marko Lahma 2 éve
szülő
commit
3a3e57c63d
29 módosított fájl, 338 hozzáadás és 200 törlés
  1. 21 17
      Jint/Collections/HybridDictionary.cs
  2. 19 1
      Jint/Collections/ListDictionary.cs
  3. 9 3
      Jint/Collections/StringDictionarySlim.cs
  4. 2 4
      Jint/Native/Global/GlobalObject.cs
  5. 5 8
      Jint/Native/Object/ObjectInstance.cs
  6. 9 6
      Jint/Runtime/Completion.cs
  7. 3 3
      Jint/Runtime/Debugger/DebugScopes.cs
  8. 14 18
      Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs
  9. 2 0
      Jint/Runtime/Environments/EnvironmentRecord.cs
  10. 169 55
      Jint/Runtime/Environments/GlobalEnvironmentRecord.cs
  11. 2 3
      Jint/Runtime/Environments/JintEnvironment.cs
  12. 0 1
      Jint/Runtime/Environments/ModuleEnvironmentRecord.cs
  13. 35 21
      Jint/Runtime/Environments/ObjectEnvironmentRecord.cs
  14. 1 1
      Jint/Runtime/Interop/DefaultTypeConverter.cs
  15. 23 24
      Jint/Runtime/Interpreter/EvaluationContext.cs
  16. 1 1
      Jint/Runtime/Interpreter/Expressions/JintConstantExpression.cs
  17. 2 1
      Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs
  18. 2 2
      Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs
  19. 2 1
      Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs
  20. 1 1
      Jint/Runtime/Interpreter/Expressions/JintSpreadExpression.cs
  21. 3 1
      Jint/Runtime/Interpreter/Expressions/JintThisExpression.cs
  22. 2 1
      Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs
  23. 2 1
      Jint/Runtime/Interpreter/Expressions/NullishCoalescingExpression.cs
  24. 1 1
      Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs
  25. 2 7
      Jint/Runtime/Interpreter/Statements/JintExportNamedDeclaration.cs
  26. 2 2
      Jint/Runtime/Interpreter/Statements/JintForStatement.cs
  27. 1 1
      Jint/Runtime/Interpreter/Statements/JintFunctionDeclarationStatement.cs
  28. 1 14
      Jint/Runtime/Interpreter/Statements/JintStatement.cs
  29. 2 1
      Jint/Runtime/Interpreter/Statements/JintSwitchStatement.cs

+ 21 - 17
Jint/Collections/HybridDictionary.cs

@@ -75,6 +75,22 @@ namespace Jint.Collections
             return false;
         }
 
+        public void SetOrUpdateValue<TState>(Key key, Func<TValue, TState, TValue> updater, TState state)
+        {
+            if (_dictionary != null)
+            {
+                _dictionary.SetOrUpdateValue(key, updater, state);
+            }
+            else if (_list != null)
+            {
+                _list.SetOrUpdateValue(key, updater, state);
+            }
+            else
+            {
+                _list = new ListDictionary<TValue>(key, updater(default, state), _checkExistingKeys);
+            }
+        }
+
         private void SwitchToDictionary(Key key, TValue value)
         {
             var dictionary = new StringDictionarySlim<TValue>(InitialDictionarySize);
@@ -122,19 +138,8 @@ namespace Jint.Collections
 
         public void Clear()
         {
-            if (_dictionary != null)
-            {
-                var dictionary = _dictionary;
-                _dictionary = null;
-                dictionary.Clear();
-            }
-
-            if (_list != null)
-            {
-                var cachedList = _list;
-                _list = null;
-                cachedList.Clear();
-            }
+            _dictionary?.Clear();
+            _list?.Clear();
         }
 
         public bool ContainsKey(Key key)
@@ -144,10 +149,9 @@ namespace Jint.Collections
                 return _dictionary.ContainsKey(key);
             }
 
-            var cachedList = _list;
-            if (cachedList != null)
+            if (_list != null)
             {
-                return cachedList.ContainsKey(key);
+                return _list.ContainsKey(key);
             }
 
             return false;
@@ -193,7 +197,7 @@ namespace Jint.Collections
 
             return _list != null && _list.Remove(key);
         }
-        
+
         /// <summary>
         /// Optimization when no need to check for existing items.
         /// </summary>

+ 19 - 1
Jint/Collections/ListDictionary.cs

@@ -17,7 +17,7 @@ namespace Jint.Collections
             _checkExistingKeys = checkExistingKeys;
             _head = new DictionaryNode
             {
-                Key = key, 
+                Key = key,
                 Value = value
             };
             _count = 1;
@@ -75,6 +75,24 @@ namespace Jint.Collections
             return false;
         }
 
+        public void SetOrUpdateValue<TState>(Key key, Func<TValue, TState, TValue> updater, TState state)
+        {
+            DictionaryNode last = null;
+            DictionaryNode node;
+            for (node = _head; node != null; node = node.Next)
+            {
+                if (node.Key == key)
+                {
+                    node.Value = updater(node.Value, state);
+                    return;
+                }
+
+                last = node;
+            }
+
+            AddNode(key, updater(default, state), last);
+        }
+
         public int Count
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]

+ 9 - 3
Jint/Collections/StringDictionarySlim.cs

@@ -141,7 +141,13 @@ namespace Jint.Collections
             return false;
         }
 
-       // Not safe for concurrent _reads_ (at least, if either of them add)
+        public void SetOrUpdateValue<TState>(Key key, Func<TValue, TState, TValue> updater, TState state)
+        {
+            ref var currentValue = ref GetOrAddValueRef(key);
+            currentValue = updater(currentValue, state);
+        }
+
+        // Not safe for concurrent _reads_ (at least, if either of them add)
         // For concurrent reads, prefer TryGetValue(key, out value)
         /// <summary>
         /// Gets the value for the specified key, or, if the key is not present,
@@ -222,7 +228,7 @@ namespace Jint.Collections
 
             return entries;
         }
-        
+
         /// <summary>
         /// Gets an enumerator over the dictionary
         /// </summary>
@@ -286,7 +292,7 @@ namespace Jint.Collections
 
             public void Dispose() { }
         }
-        
+
         internal static class HashHelpers
         {
             internal static readonly int[] SizeOneIntArray = new int[1];

+ 2 - 4
Jint/Native/Global/GlobalObject.cs

@@ -21,8 +21,6 @@ namespace Jint.Native.Global
             Realm realm) : base(engine)
         {
             _realm = realm;
-            // this is implementation dependent, and only to pass some unit tests
-            _prototype = realm.Intrinsics.Object.PrototypeObject;
         }
 
         protected override void Initialize()
@@ -798,14 +796,14 @@ namespace Jint.Native.Global
             return descriptor ?? PropertyDescriptor.Undefined;
         }
 
-        internal bool Set(Key property, JsValue value)
+        internal bool SetFromMutableBinding(Key property, JsValue value)
         {
             // here we are called only from global environment record context
             // we can take some shortcuts to be faster
 
             if (!_properties!.TryGetValue(property, out var existingDescriptor))
             {
-                _properties[property] = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable);
+                _properties[property] = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding);
                 return true;
             }
 

+ 5 - 8
Jint/Native/Object/ObjectInstance.cs

@@ -643,21 +643,18 @@ namespace Jint.Native.Object
         }
 
         /// <summary>
-        /// Creates or alters the named own property to
-        /// have the state described by a Property
-        /// Descriptor. The flag controls failure handling.
+        /// Creates or alters the named own property to have the state described by a PropertyDescriptor.
         /// </summary>
         public virtual bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
         {
             var current = GetOwnProperty(property);
-            var extensible = Extensible;
 
             if (current == desc)
             {
                 return true;
             }
 
-            return ValidateAndApplyPropertyDescriptor(this, property, extensible, desc, current);
+            return ValidateAndApplyPropertyDescriptor(this, property, Extensible, desc, current);
         }
 
         /// <summary>
@@ -673,7 +670,7 @@ namespace Jint.Native.Object
                     return false;
                 }
 
-                if (o is object)
+                if (o is not null)
                 {
                     if (desc.IsGenericDescriptor() || desc.IsDataDescriptor())
                     {
@@ -762,7 +759,7 @@ namespace Jint.Native.Object
                         return false;
                     }
 
-                    if (o is object)
+                    if (o is not null)
                     {
                         var flags = current.Flags & ~(PropertyFlag.Writable | PropertyFlag.WritableSet | PropertyFlag.CustomJsValue);
                         if (current.IsDataDescriptor())
@@ -814,7 +811,7 @@ namespace Jint.Native.Object
                 }
             }
 
-            if (o is object)
+            if (o is not null)
             {
                 if (!ReferenceEquals(descValue, null))
                 {

+ 9 - 6
Jint/Runtime/Completion.cs

@@ -22,7 +22,7 @@ namespace Jint.Runtime
     [StructLayout(LayoutKind.Auto)]
     public readonly struct Completion
     {
-        internal static readonly Node _emptyNode = new Identifier("");
+        private static readonly Node _emptyNode = new Identifier("");
         private static readonly Completion _emptyCompletion = new(CompletionType.Normal, null!, _emptyNode);
 
         internal readonly SyntaxElement _source;
@@ -36,13 +36,19 @@ namespace Jint.Runtime
         }
 
         public Completion(CompletionType type, JsValue value, SyntaxElement source)
-            : this(type, value, null, source)
         {
+            Type = type;
+            Value = value;
+            Target = null;
+            _source = source;
         }
 
         public Completion(CompletionType type, string target, SyntaxElement source)
-            : this(type, null!, target, source)
         {
+            Type = type;
+            Value = null!;
+            Target = target;
+            _source = source;
         }
 
         internal Completion(in ExpressionResult result)
@@ -59,9 +65,6 @@ namespace Jint.Runtime
         public readonly string? Target;
         public ref readonly Location Location => ref _source.Location;
 
-        public static Completion Normal(JsValue value, Node source)
-            => new Completion(CompletionType.Normal, value, source);
-
         public static ref readonly Completion Empty() => ref _emptyCompletion;
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]

+ 3 - 3
Jint/Runtime/Debugger/DebugScopes.cs

@@ -5,8 +5,8 @@ namespace Jint.Runtime.Debugger
 {
     public sealed class DebugScopes : IReadOnlyList<DebugScope>
     {
-        private readonly HashSet<string> _foundBindings = new HashSet<string>();
-        private readonly List<DebugScope> _scopes = new List<DebugScope>();
+        private readonly HashSet<string> _foundBindings = new();
+        private readonly List<DebugScope> _scopes = new();
 
         internal DebugScopes(EnvironmentRecord environment)
         {
@@ -44,7 +44,7 @@ namespace Jint.Runtime.Debugger
                     case GlobalEnvironmentRecord global:
                         // Similarly to Chromium, we split the Global environment into Global and Script scopes
                         AddScope(DebugScopeType.Script, global._declarativeRecord);
-                        AddScope(DebugScopeType.Global, global._objectRecord);
+                        AddScope(DebugScopeType.Global, new ObjectEnvironmentRecord(environment._engine, global._global, false, false));
                         break;
                     case FunctionEnvironmentRecord:
                         AddScope(inLocalScope ? DebugScopeType.Local : DebugScopeType.Closure, record);

+ 14 - 18
Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs

@@ -12,7 +12,6 @@ namespace Jint.Runtime.Environments
     internal class DeclarativeEnvironmentRecord : EnvironmentRecord
     {
         internal readonly HybridDictionary<Binding> _dictionary = new();
-        internal bool _hasBindings;
         internal readonly bool _catchEnvironment;
 
         public DeclarativeEnvironmentRecord(Engine engine, bool catchEnvironment = false) : base(engine)
@@ -20,11 +19,16 @@ namespace Jint.Runtime.Environments
             _catchEnvironment = catchEnvironment;
         }
 
-        public override bool HasBinding(string name)
+        public sealed override bool HasBinding(string name)
         {
             return _dictionary.ContainsKey(name);
         }
 
+        internal sealed override bool HasBinding(in BindingName name)
+        {
+            return _dictionary.ContainsKey(name.Key);
+        }
+
         internal override bool TryGetBinding(
             in BindingName name,
             bool strict,
@@ -39,36 +43,30 @@ namespace Jint.Runtime.Environments
 
         internal void CreateMutableBindingAndInitialize(Key name, bool canBeDeleted, JsValue value)
         {
-            _hasBindings = true;
             _dictionary[name] = new Binding(value, canBeDeleted, mutable: true, strict: false);
         }
 
         internal void CreateImmutableBindingAndInitialize(Key name, bool strict, JsValue value)
         {
-            _hasBindings = true;
             _dictionary[name] = new Binding(value, canBeDeleted: false, mutable: false, strict);
         }
 
         public sealed override void CreateMutableBinding(string name, bool canBeDeleted = false)
         {
-            _hasBindings = true;
             _dictionary[name] = new Binding(null!, canBeDeleted, mutable: true, strict: false);
         }
 
         public sealed override void CreateImmutableBinding(string name, bool strict = true)
         {
-            _hasBindings = true;
             _dictionary[name] = new Binding(null!, canBeDeleted: false, mutable: false, strict);
         }
 
         public sealed override void InitializeBinding(string name, JsValue value)
         {
-            _hasBindings = true;
-            _dictionary.TryGetValue(name, out var binding);
-            _dictionary[name] = binding.ChangeValue(value);
+            _dictionary.SetOrUpdateValue(name, static (current, value) => current.ChangeValue(value), value);
         }
 
-        internal override void SetMutableBinding(in BindingName name, JsValue value, bool strict)
+        internal sealed override void SetMutableBinding(in BindingName name, JsValue value, bool strict)
         {
             SetMutableBinding(name.Key.Name, value, strict);
         }
@@ -80,7 +78,7 @@ namespace Jint.Runtime.Environments
             {
                 if (strict)
                 {
-                    ExceptionHelper.ThrowReferenceNameError(_engine.Realm, key);
+                    ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name);
                 }
                 CreateMutableBindingAndInitialize(key, canBeDeleted: true, value);
                 return;
@@ -94,12 +92,11 @@ namespace Jint.Runtime.Environments
             // Is it an uninitialized binding?
             if (!binding.IsInitialized())
             {
-                ExceptionHelper.ThrowReferenceError(_engine.Realm, "Cannot access '" +  key + "' before initialization");
+                ThrowUninitializedBindingError(name);
             }
 
             if (binding.Mutable)
             {
-                _hasBindings = true;
                 _dictionary[key] = binding.ChangeValue(value);
             }
             else
@@ -123,7 +120,7 @@ namespace Jint.Runtime.Environments
             return null!;
         }
 
-        internal override bool TryGetBindingValue(string name, bool strict, [NotNullWhen(true)] out JsValue? value)
+        internal sealed override bool TryGetBindingValue(string name, bool strict, [NotNullWhen(true)] out JsValue? value)
         {
             _dictionary.TryGetValue(name, out var binding);
             if (binding.IsInitialized())
@@ -154,7 +151,6 @@ namespace Jint.Runtime.Environments
             }
 
             _dictionary.Remove(name);
-            _hasBindings = _dictionary.Count > 0;
 
             return true;
         }
@@ -163,14 +159,14 @@ namespace Jint.Runtime.Environments
 
         public override bool HasSuperBinding() => false;
 
-        public override JsValue WithBaseObject() => Undefined;
+        public sealed override JsValue WithBaseObject() => Undefined;
 
         /// <inheritdoc />
         internal sealed override string[] GetAllBindingNames()
         {
             if (_dictionary is null)
             {
-                return System.Array.Empty<string>();
+                return Array.Empty<string>();
             }
 
             var keys = new string[_dictionary.Count];
@@ -185,7 +181,7 @@ namespace Jint.Runtime.Environments
 
         public override JsValue GetThisBinding()
         {
-            throw new System.NotImplementedException();
+            return Undefined;
         }
     }
 }

+ 2 - 0
Jint/Runtime/Environments/EnvironmentRecord.cs

@@ -25,6 +25,8 @@ namespace Jint.Runtime.Environments
         /// <returns><c>true</c> if it does and <c>false</c> if it does not.</returns>
         public abstract bool HasBinding(string name);
 
+        internal abstract bool HasBinding(in BindingName name);
+
         internal abstract bool TryGetBinding(
             in BindingName name,
             bool strict,

+ 169 - 55
Jint/Runtime/Environments/GlobalEnvironmentRecord.cs

@@ -12,16 +12,19 @@ namespace Jint.Runtime.Environments
     /// </summary>
     public sealed class GlobalEnvironmentRecord : EnvironmentRecord
     {
-        private readonly ObjectInstance _global;
+        internal readonly ObjectInstance _global;
+
+        // we expect it to be GlobalObject, but need to allow to something host-defined, like Window
+        private readonly GlobalObject? _globalObject;
+
         // Environment records are needed by debugger
         internal readonly DeclarativeEnvironmentRecord _declarativeRecord;
-        internal readonly ObjectEnvironmentRecord _objectRecord;
-        private readonly HashSet<string> _varNames = new HashSet<string>();
+        private readonly HashSet<string> _varNames = new();
 
         public GlobalEnvironmentRecord(Engine engine, ObjectInstance global) : base(engine)
         {
             _global = global;
-            _objectRecord = new ObjectEnvironmentRecord(engine, global, provideThis: false, withEnvironment: false);
+            _globalObject = global as GlobalObject;
             _declarativeRecord = new DeclarativeEnvironmentRecord(engine);
         }
 
@@ -29,7 +32,32 @@ namespace Jint.Runtime.Environments
 
         public override bool HasBinding(string name)
         {
-            return (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name)) || _objectRecord.HasBinding(name);
+            if (_declarativeRecord.HasBinding(name))
+            {
+                return true;
+            }
+
+            if (_globalObject is not null)
+            {
+                return _globalObject.HasProperty(name);
+            }
+
+            return _global.HasProperty(new JsString(name));
+        }
+
+        internal override bool HasBinding(in BindingName name)
+        {
+            if (_declarativeRecord.HasBinding(name))
+            {
+                return true;
+            }
+
+            if (_globalObject is not null)
+            {
+                return _globalObject.HasProperty(name.Key);
+            }
+
+            return _global.HasProperty(name.StringValue);
         }
 
         internal override bool TryGetBinding(
@@ -38,8 +66,7 @@ namespace Jint.Runtime.Environments
             out Binding binding,
             [NotNullWhen(true)] out JsValue? value)
         {
-            if (_declarativeRecord._hasBindings &&
-                _declarativeRecord.TryGetBinding(name, strict, out binding, out value))
+            if (_declarativeRecord.TryGetBinding(name, strict, out binding, out value))
             {
                 return true;
             }
@@ -56,22 +83,23 @@ namespace Jint.Runtime.Environments
                 return true;
             }
 
-            return TryGetBindingForGlobalParent(name, out value, property);
+            if (_global._prototype is not null)
+            {
+                return TryGetBindingForGlobalParent(name, out value);
+            }
+
+            return false;
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
         private bool TryGetBindingForGlobalParent(
             in BindingName name,
-            [NotNullWhen(true)] out JsValue? value,
-            PropertyDescriptor property)
+            [NotNullWhen(true)] out JsValue? value)
         {
             value = default;
 
-            var parent = _global._prototype;
-            if (parent is not null)
-            {
-                property = parent.GetOwnProperty(name.StringValue);
-            }
+            var parent = _global._prototype!;
+            var property = parent.GetOwnProperty(name.StringValue);
 
             if (property == PropertyDescriptor.Undefined)
             {
@@ -83,110 +111,177 @@ namespace Jint.Runtime.Environments
         }
 
         /// <summary>
-        ///     http://www.ecma-international.org/ecma-262/5.1/#sec-10.2.1.2.2
+        /// https://tc39.es/ecma262/#sec-global-environment-records-createmutablebinding-n-d
         /// </summary>
         public override void CreateMutableBinding(string name, bool canBeDeleted = false)
         {
-            if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
+            if (_declarativeRecord.HasBinding(name))
             {
-                ExceptionHelper.ThrowTypeError(_engine.Realm, name + " has already been declared");
+                ThrowAlreadyDeclaredException(name);
             }
 
             _declarativeRecord.CreateMutableBinding(name, canBeDeleted);
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-global-environment-records-createimmutablebinding-n-s
+        /// </summary>
         public override void CreateImmutableBinding(string name, bool strict = true)
         {
-            if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
+            if (_declarativeRecord.HasBinding(name))
             {
-                ExceptionHelper.ThrowTypeError(_engine.Realm, name + " has already been declared");
+                ThrowAlreadyDeclaredException(name);
             }
 
             _declarativeRecord.CreateImmutableBinding(name, strict);
         }
 
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private void ThrowAlreadyDeclaredException(string name)
+        {
+            ExceptionHelper.ThrowTypeError(_engine.Realm, name + " has already been declared");
+        }
+
         public override void InitializeBinding(string name, JsValue value)
         {
-            if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
+            if (_declarativeRecord.HasBinding(name))
             {
                 _declarativeRecord.InitializeBinding(name, value);
             }
             else
             {
-                if (!_global.Set(name, value))
-                {
-                    ExceptionHelper.ThrowTypeError(_engine.Realm);
-                }
+                _global._properties![name].Value = value;
             }
         }
 
         public override void SetMutableBinding(string name, JsValue value, bool strict)
         {
-            if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
+            if (_declarativeRecord.HasBinding(name))
             {
                 _declarativeRecord.SetMutableBinding(name, value, strict);
             }
             else
             {
-                // fast inlined path as we know we target global, otherwise would be
-                // _objectRecord.SetMutableBinding(name, value, strict);
-                var property = JsString.Create(name);
-                if (strict && !_global.HasProperty(property))
+                if (_globalObject is not null)
                 {
-                    ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name);
+                    // fast inlined path as we know we target global
+                    if (!_globalObject.SetFromMutableBinding(name, value) && strict)
+                    {
+                        ExceptionHelper.ThrowTypeError(_engine.Realm);
+                    }
+                }
+                else
+                {
+                    SetMutableBindingUnlikely(name, value, strict);
                 }
-
-                _global.Set(property, value);
             }
         }
 
         internal override void SetMutableBinding(in BindingName name, JsValue value, bool strict)
         {
-            if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name.Key.Name))
+            if (_declarativeRecord.HasBinding(name))
             {
-                _declarativeRecord.SetMutableBinding(name.Key.Name, value, strict);
+                _declarativeRecord.SetMutableBinding(name, value, strict);
             }
             else
             {
-                if (_global is GlobalObject globalObject)
+                if (_globalObject is not null)
                 {
                     // fast inlined path as we know we target global
-                    if (!globalObject.Set(name.Key, value) && strict)
+                    if (!_globalObject.SetFromMutableBinding(name.Key, value) && strict)
                     {
                         ExceptionHelper.ThrowTypeError(_engine.Realm);
                     }
                 }
                 else
                 {
-                    _objectRecord.SetMutableBinding(name, value ,strict);
+                    SetMutableBindingUnlikely(name.Key.Name, value, strict);
                 }
             }
         }
 
+        private void SetMutableBindingUnlikely(string name, JsValue value, bool strict)
+        {
+            // see ObjectEnvironmentRecord.SetMutableBinding
+            var jsString = new JsString(name);
+            if (strict && !_global.HasProperty(jsString))
+            {
+                ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name);
+            }
+
+            _global.Set(jsString, value);
+        }
+
         public override JsValue GetBindingValue(string name, bool strict)
         {
-            return _declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name)
-                ? _declarativeRecord.GetBindingValue(name, strict)
-                : _objectRecord.GetBindingValue(name, strict);
+            if (_declarativeRecord.HasBinding(name))
+            {
+                return _declarativeRecord.GetBindingValue(name, strict);
+            }
+
+            // see ObjectEnvironmentRecord.GetBindingValue
+            var desc = PropertyDescriptor.Undefined;
+            if (_globalObject is not null)
+            {
+                if (_globalObject._properties?.TryGetValue(name, out desc) == false)
+                {
+                    desc = PropertyDescriptor.Undefined;
+                }
+            }
+            else
+            {
+                desc = _global.GetProperty(name);
+            }
+
+            if (strict && desc == PropertyDescriptor.Undefined)
+            {
+                ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name);
+            }
+
+            return ObjectInstance.UnwrapJsValue(desc, _global);
         }
 
         internal override bool TryGetBindingValue(string name, bool strict, [NotNullWhen(true)] out JsValue? value)
         {
-            return _declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name)
-                ? _declarativeRecord.TryGetBindingValue(name, strict, out value)
-                : _objectRecord.TryGetBindingValue(name, strict, out value);
+            if (_declarativeRecord.HasBinding(name))
+            {
+                return _declarativeRecord.TryGetBindingValue(name, strict, out value);
+            }
+
+            // see ObjectEnvironmentRecord.TryGetBindingValue
+            var desc = PropertyDescriptor.Undefined;
+            if (_globalObject is not null)
+            {
+                if (_globalObject._properties?.TryGetValue(name, out desc) == false)
+                {
+                    desc = PropertyDescriptor.Undefined;
+                }
+            }
+            else
+            {
+                desc = _global.GetProperty(name);
+            }
+
+            if (strict && desc == PropertyDescriptor.Undefined)
+            {
+                value = null;
+                return false;
+            }
+
+            value = ObjectInstance.UnwrapJsValue(desc, _global);
+            return true;
         }
 
         public override bool DeleteBinding(string name)
         {
-            if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
+            if (_declarativeRecord.HasBinding(name))
             {
                 return _declarativeRecord.DeleteBinding(name);
             }
 
             if (_global.HasOwnProperty(name))
             {
-                var status = _objectRecord.DeleteBinding(name);
+                var status = _global.Delete(name);
                 if (status)
                 {
                     _varNames.Remove(name);
@@ -230,6 +325,12 @@ namespace Jint.Runtime.Environments
 
         public bool HasRestrictedGlobalProperty(string name)
         {
+            if (_globalObject is not null)
+            {
+                return _globalObject._properties?.TryGetValue(name, out var desc) == true
+                       && !desc.Configurable;
+            }
+
             var existingProp = _global.GetOwnProperty(name);
             if (existingProp == PropertyDescriptor.Undefined)
             {
@@ -272,10 +373,12 @@ namespace Jint.Runtime.Environments
 
         public void CreateGlobalVarBinding(string name, bool canBeDeleted)
         {
-            var hasProperty = _global.HasOwnProperty(name);
-            if (!hasProperty && _global.Extensible)
+            Key key = name;
+            if (!_global._properties!.ContainsKey(key) && _global.Extensible)
             {
-                _objectRecord.CreateMutableBindingAndInitialize(name, Undefined, canBeDeleted);
+                _global._properties[key] = new PropertyDescriptor(Undefined, canBeDeleted
+                    ? PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding
+                    : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding);
             }
 
             _varNames.Add(name);
@@ -286,7 +389,8 @@ namespace Jint.Runtime.Environments
         /// </summary>
         public void CreateGlobalFunctionBinding(string name, JsValue value, bool canBeDeleted)
         {
-            var existingProp = _global.GetOwnProperty(name);
+            var jsString = new JsString(name);
+            var existingProp = _global.GetOwnProperty(jsString);
 
             PropertyDescriptor desc;
             if (existingProp == PropertyDescriptor.Undefined || existingProp.Configurable)
@@ -298,8 +402,8 @@ namespace Jint.Runtime.Environments
                 desc = new PropertyDescriptor(value, PropertyFlag.None);
             }
 
-            _global.DefinePropertyOrThrow(name, desc);
-            _global.Set(name, value, false);
+            _global.DefinePropertyOrThrow(jsString, desc);
+            _global.Set(jsString, value, false);
             _varNames.Add(name);
         }
 
@@ -308,13 +412,23 @@ namespace Jint.Runtime.Environments
             // JT: Rather than introduce a new method for the debugger, I'm reusing this one,
             // which - in spite of the very general name - is actually only used by the debugger
             // at this point.
-            return _global.GetOwnProperties().Select(x => x.Key.ToString())
-                .Concat(_declarativeRecord.GetAllBindingNames()).ToArray();
+            var names = new List<string>(_global._properties?.Count ?? 0 + _declarativeRecord._dictionary?.Count ?? 0);
+            foreach (var name in _global.GetOwnProperties())
+            {
+                names.Add(name.Key.ToString());
+            }
+
+            foreach (var name in _declarativeRecord.GetAllBindingNames())
+            {
+                names.Add(name);
+            }
+
+            return names.ToArray();
         }
 
         public override bool Equals(JsValue? other)
         {
-            return ReferenceEquals(_objectRecord, other);
+            return ReferenceEquals(this, other);
         }
     }
 }

+ 2 - 3
Jint/Runtime/Environments/JintEnvironment.cs

@@ -14,15 +14,14 @@ namespace Jint.Runtime.Environments
         {
             record = env;
 
-            var keyName = name.Key.Name;
             if (env._outerEnv is null)
             {
-                return env.HasBinding(keyName);
+                return env.HasBinding(name);
             }
 
             while (!ReferenceEquals(record, null))
             {
-                if (record.HasBinding(keyName))
+                if (record.HasBinding(name))
                 {
                     return true;
                 }

+ 0 - 1
Jint/Runtime/Environments/ModuleEnvironmentRecord.cs

@@ -30,7 +30,6 @@ internal sealed class ModuleEnvironmentRecord : DeclarativeEnvironmentRecord
     /// </summary>
     public void CreateImportBinding(string importName, ModuleRecord module, string name)
     {
-        _hasBindings = true;
         _importBindings[importName] = new IndirectBinding(module, name);
         CreateImmutableBindingAndInitialize(importName, true, JsValue.Undefined);
     }

+ 35 - 21
Jint/Runtime/Environments/ObjectEnvironmentRecord.cs

@@ -45,6 +45,23 @@ namespace Jint.Runtime.Environments
             return !IsBlocked(name);
         }
 
+        internal override bool HasBinding(in BindingName name)
+        {
+            var foundBinding = HasProperty(name.StringValue);
+
+            if (!foundBinding)
+            {
+                return false;
+            }
+
+            if (!_withEnvironment)
+            {
+                return true;
+            }
+
+            return !IsBlocked(name.StringValue);
+        }
+
         private bool HasProperty(JsValue property)
         {
             return _bindingObject.HasProperty(property);
@@ -96,27 +113,13 @@ namespace Jint.Runtime.Environments
         /// </summary>
         public override void CreateMutableBinding(string name, bool canBeDeleted = false)
         {
-            var propertyDescriptor = canBeDeleted
-                ? new PropertyDescriptor(Undefined, PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding)
-                : new PropertyDescriptor(Undefined, PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding);
-
-            _bindingObject.DefinePropertyOrThrow(name, propertyDescriptor);
-        }
-
-        /// <summary>
-        /// http://www.ecma-international.org/ecma-262/6.0/#sec-object-environment-records-createmutablebinding-n-d
-        /// </summary>
-        internal void CreateMutableBindingAndInitialize(string name, JsValue value, bool canBeDeleted = false)
-        {
-            var propertyDescriptor = canBeDeleted
-                ? new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding)
-                : new PropertyDescriptor(value, PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding);
-
-            _bindingObject.DefinePropertyOrThrow(name, propertyDescriptor);
+            _bindingObject.DefinePropertyOrThrow(name, new PropertyDescriptor(Undefined, canBeDeleted
+                ? PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding
+                : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding));
         }
 
         /// <summary>
-        ///  http://www.ecma-international.org/ecma-262/6.0/#sec-object-environment-records-createimmutablebinding-n-s
+        /// https://tc39.es/ecma262/#sec-object-environment-records-createimmutablebinding-n-s
         /// </summary>
         public override void CreateImmutableBinding(string name, bool strict = true)
         {
@@ -124,7 +127,7 @@ namespace Jint.Runtime.Environments
         }
 
         /// <summary>
-        /// http://www.ecma-international.org/ecma-262/6.0/#sec-object-environment-records-initializebinding-n-v
+        /// https://tc39.es/ecma262/#sec-object-environment-records-initializebinding-n-v
         /// </summary>
         public override void InitializeBinding(string name, JsValue value)
         {
@@ -133,7 +136,13 @@ namespace Jint.Runtime.Environments
 
         public override void SetMutableBinding(string name, JsValue value, bool strict)
         {
-            SetMutableBinding(new BindingName(name), value, strict);
+            var jsString = new JsString(name);
+            if (strict && !_bindingObject.HasProperty(jsString))
+            {
+                ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name);
+            }
+
+            _bindingObject.Set(jsString, value);
         }
 
         internal override void SetMutableBinding(in BindingName name, JsValue value, bool strict)
@@ -186,7 +195,12 @@ namespace Jint.Runtime.Environments
         {
             if (!ReferenceEquals(_bindingObject, null))
             {
-                return _bindingObject.GetOwnProperties().Select( x=> x.Key.ToString()).ToArray();
+                var names = new List<string>(_bindingObject._properties?.Count ?? 0);
+                foreach (var name in _bindingObject.GetOwnProperties())
+                {
+                    names.Add(name.Key.ToString());
+                }
+                return names.ToArray();
             }
 
             return Array.Empty<string>();

+ 1 - 1
Jint/Runtime/Interop/DefaultTypeConverter.cs

@@ -255,7 +255,7 @@ namespace Jint.Runtime.Interop
                 new ReadOnlyCollection<ParameterExpression>(parameters)).Compile();
         }
 
-        private bool TryCastWithOperators(object value, Type type, Type valueType, [NotNullWhen(true)] out object? converted)
+        private static bool TryCastWithOperators(object value, Type type, Type valueType, [NotNullWhen(true)] out object? converted)
         {
             var key = new TypeConversionKey(valueType, type);
 

+ 23 - 24
Jint/Runtime/Interpreter/EvaluationContext.cs

@@ -1,35 +1,34 @@
 using Esprima.Ast;
 
-namespace Jint.Runtime.Interpreter
+namespace Jint.Runtime.Interpreter;
+
+/// <summary>
+/// Per Engine.Evaluate() call context.
+/// </summary>
+internal sealed class EvaluationContext
 {
-    /// <summary>
-    /// Per Engine.Evalute() call context.
-    /// </summary>
-    internal sealed class EvaluationContext
-    {
-        private readonly bool _shouldRunBeforeExecuteStatementChecks;
+    private readonly bool _shouldRunBeforeExecuteStatementChecks;
 
-        public EvaluationContext(Engine engine, in Completion? resumedCompletion = null)
-        {
-            Engine = engine;
-            ResumedCompletion = resumedCompletion ?? default; // TODO later
-            OperatorOverloadingAllowed = engine.Options.Interop.AllowOperatorOverloading;
-            _shouldRunBeforeExecuteStatementChecks = engine._constraints.Length > 0 || engine._isDebugMode;
-        }
+    public EvaluationContext(Engine engine, in Completion? resumedCompletion = null)
+    {
+        Engine = engine;
+        ResumedCompletion = resumedCompletion ?? default; // TODO later
+        OperatorOverloadingAllowed = engine.Options.Interop.AllowOperatorOverloading;
+        _shouldRunBeforeExecuteStatementChecks = engine._constraints.Length > 0 || engine._isDebugMode;
+    }
 
-        public Engine Engine { get; }
-        public Completion ResumedCompletion { get; }
-        public bool DebugMode => Engine._isDebugMode;
+    public readonly Engine Engine;
+    public readonly Completion ResumedCompletion;
+    public bool DebugMode => Engine._isDebugMode;
 
-        public SyntaxElement LastSyntaxElement { get; set; } = null!;
-        public bool OperatorOverloadingAllowed { get; }
+    public SyntaxElement LastSyntaxElement = null!;
+    public readonly bool OperatorOverloadingAllowed;
 
-        public void RunBeforeExecuteStatementChecks(Statement statement)
+    public void RunBeforeExecuteStatementChecks(Statement statement)
+    {
+        if (_shouldRunBeforeExecuteStatementChecks)
         {
-            if (_shouldRunBeforeExecuteStatementChecks)
-            {
-                Engine.RunBeforeExecuteStatementChecks(statement);
-            }
+            Engine.RunBeforeExecuteStatementChecks(statement);
         }
     }
 }

+ 1 - 1
Jint/Runtime/Interpreter/Expressions/JintConstantExpression.cs

@@ -20,7 +20,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             // need to notify correct node when taking shortcut
             context.LastSyntaxElement = _expression;
 
-            return Completion.Normal(_value, _expression);
+            return new(CompletionType.Normal, _value, _expression);
         }
 
         protected override ExpressionResult EvaluateInternal(EvaluationContext context) => NormalCompletion(_value);

+ 2 - 1
Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs

@@ -1,4 +1,5 @@
 using Esprima.Ast;
+using Jint.Native;
 using Jint.Native.Function;
 using Jint.Runtime.Environments;
 
@@ -24,7 +25,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 ? InstantiateOrdinaryFunctionExpression(context, _function.Name!)
                 : InstantiateGeneratorFunctionExpression(context, _function.Name!);
 
-            return Completion.Normal(closure, _expression);
+            return new(CompletionType.Normal, closure, _expression);
         }
 
         /// <summary>

+ 2 - 2
Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs

@@ -41,7 +41,7 @@ namespace Jint.Runtime.Interpreter.Expressions
 
             if (_calculatedValue is not null)
             {
-                return Completion.Normal(_calculatedValue, _expression);
+                return new(CompletionType.Normal, _calculatedValue, _expression);
             }
 
             var strict = StrictModeScope.IsStrictModeCode;
@@ -72,7 +72,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 argumentsInstance.Materialize();
             }
 
-            return Completion.Normal(value, _expression);
+            return new(CompletionType.Normal, value, _expression);
         }
     }
 }

+ 2 - 1
Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs

@@ -63,7 +63,8 @@ namespace Jint.Runtime.Interpreter.Expressions
             // need to notify correct node when taking shortcut
             context.LastSyntaxElement = _expression;
 
-            return Completion.Normal(ResolveValue(context), _expression);
+            JsValue value = ResolveValue(context);
+            return new(CompletionType.Normal, value, _expression);
         }
 
         protected override ExpressionResult EvaluateInternal(EvaluationContext context) => NormalCompletion(ResolveValue(context));

+ 1 - 1
Jint/Runtime/Interpreter/Expressions/JintSpreadExpression.cs

@@ -27,7 +27,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             context.LastSyntaxElement = _expression;
 
             GetValueAndCheckIterator(context, out var objectInstance, out var iterator);
-            return Completion.Normal(objectInstance, _expression);
+            return new(CompletionType.Normal, objectInstance, _expression);
         }
 
         internal void GetValueAndCheckIterator(EvaluationContext context, out JsValue instance, out IteratorInstance? iterator)

+ 3 - 1
Jint/Runtime/Interpreter/Expressions/JintThisExpression.cs

@@ -1,4 +1,5 @@
 using Esprima.Ast;
+using Jint.Native;
 
 namespace Jint.Runtime.Interpreter.Expressions
 {
@@ -18,7 +19,8 @@ namespace Jint.Runtime.Interpreter.Expressions
             // need to notify correct node when taking shortcut
             context.LastSyntaxElement = _expression;
 
-            return Completion.Normal(context.Engine.ResolveThisBinding(), _expression);
+            JsValue value = context.Engine.ResolveThisBinding();
+            return new(CompletionType.Normal, value, _expression);
         }
     }
 }

+ 2 - 1
Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs

@@ -46,7 +46,8 @@ namespace Jint.Runtime.Interpreter.Expressions
             // need to notify correct node when taking shortcut
             context.LastSyntaxElement = _expression;
 
-            return Completion.Normal(EvaluateJsValue(context), _expression);
+            JsValue value = EvaluateJsValue(context);
+            return new(CompletionType.Normal, value, _expression);
         }
 
         protected override ExpressionResult EvaluateInternal(EvaluationContext context)

+ 2 - 1
Jint/Runtime/Interpreter/Expressions/NullishCoalescingExpression.cs

@@ -29,7 +29,8 @@ namespace Jint.Runtime.Interpreter.Expressions
         {
             // need to notify correct node when taking shortcut
             context.LastSyntaxElement = _expression;
-            return Completion.Normal(EvaluateConstantOrExpression(context), _expression);
+            JsValue value = EvaluateConstantOrExpression(context);
+            return new(CompletionType.Normal, value, _expression);
         }
 
         protected override ExpressionResult EvaluateInternal(EvaluationContext context)

+ 1 - 1
Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs

@@ -58,7 +58,7 @@ namespace Jint.Runtime.Interpreter.Statements
                 iterating = TypeConverter.ToBoolean(_test.GetValue(context).Value);
             } while (iterating);
 
-            return NormalCompletion(v);
+            return new Completion(CompletionType.Normal, v, ((JintStatement) this)._statement);
         }
     }
 }

+ 2 - 7
Jint/Runtime/Interpreter/Statements/JintExportNamedDeclaration.cs

@@ -24,12 +24,7 @@ internal sealed class JintExportNamedDeclaration : JintStatement<ExportNamedDecl
     /// </summary>
     protected override Completion ExecuteInternal(EvaluationContext context)
     {
-        if (_declarationStatement != null)
-        {
-            _declarationStatement.Execute(context);
-            return NormalCompletion(Undefined.Instance);
-        }
-
-        return NormalCompletion(Undefined.Instance);
+        _declarationStatement?.Execute(context);
+        return new Completion(CompletionType.Normal, Undefined.Instance, ((JintStatement) this)._statement);
     }
 }

+ 2 - 2
Jint/Runtime/Interpreter/Statements/JintForStatement.cs

@@ -131,7 +131,7 @@ namespace Jint.Runtime.Interpreter.Statements
 
                     if (!TypeConverter.ToBoolean(_test.GetValue(context).Value))
                     {
-                        return NormalCompletion(v);
+                        return new Completion(CompletionType.Normal, v, ((JintStatement) this)._statement);
                     }
                 }
 
@@ -143,7 +143,7 @@ namespace Jint.Runtime.Interpreter.Statements
 
                 if (result.Type == CompletionType.Break && (result.Target == null || result.Target == _statement?.LabelSet?.Name))
                 {
-                    return NormalCompletion(result.Value!);
+                    return new Completion(CompletionType.Normal, result.Value!, ((JintStatement) this)._statement);
                 }
 
                 if (result.Type != CompletionType.Continue || (result.Target != null && result.Target != _statement?.LabelSet?.Name))

+ 1 - 1
Jint/Runtime/Interpreter/Statements/JintFunctionDeclarationStatement.cs

@@ -11,7 +11,7 @@ namespace Jint.Runtime.Interpreter.Statements
 
         protected override Completion ExecuteInternal(EvaluationContext context)
         {
-            return NormalCompletion(JsValue.Undefined);
+            return new Completion(CompletionType.Normal, JsValue.Undefined, ((JintStatement) this)._statement);
         }
     }
 }

+ 1 - 14
Jint/Runtime/Interpreter/Statements/JintStatement.cs

@@ -44,7 +44,7 @@ namespace Jint.Runtime.Interpreter.Statements
 
             if (context.ResumedCompletion.IsAbrupt() && !SupportsResume)
             {
-                return NormalCompletion(JsValue.Undefined);
+                return new Completion(CompletionType.Normal, JsValue.Undefined, _statement);
             }
 
             return ExecuteInternal(context);
@@ -117,18 +117,5 @@ namespace Jint.Runtime.Interpreter.Statements
 
             return null;
         }
-
-        /// <summary>
-        /// https://tc39.es/ecma262/#sec-normalcompletion
-        /// </summary>
-        /// <remarks>
-        /// We use custom type that is translated to Completion later on.
-        /// </remarks>
-        [DebuggerStepThrough]
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        protected Completion NormalCompletion(JsValue value)
-        {
-            return new Completion(CompletionType.Normal, value, _statement);
-        }
     }
 }

+ 2 - 1
Jint/Runtime/Interpreter/Statements/JintSwitchStatement.cs

@@ -1,4 +1,5 @@
 using Esprima.Ast;
+using Jint.Native;
 using Jint.Runtime.Interpreter.Expressions;
 
 namespace Jint.Runtime.Interpreter.Statements
@@ -28,7 +29,7 @@ namespace Jint.Runtime.Interpreter.Statements
             var r = _switchBlock.Execute(context, value);
             if (r.Type == CompletionType.Break && r.Target == _statement.LabelSet?.Name)
             {
-                return NormalCompletion(r.Value);
+                return new Completion(CompletionType.Normal, r.Value, ((JintStatement) this)._statement);
             }
 
             return r;