Skip to content

Commit

Permalink
Implement FinalizationRegistry
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma committed Oct 23, 2022
1 parent ca6c0d6 commit 7c5b87a
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 6 deletions.
2 changes: 0 additions & 2 deletions Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
"class-static-fields-public",
"class-static-methods-private",
"decorators",
"FinalizationRegistry",
"FinalizationRegistry.prototype.cleanupSome",
"generators",
"import-assertions",
"regexp-duplicate-named-groups",
Expand Down
1 change: 1 addition & 0 deletions Jint.Tests.Test262/Test262Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ private Engine BuildTestExecutor(Test262File file)
(_, _) =>
{
GC.Collect();
GC.WaitForPendingFinalizers();
return JsValue.Undefined;
}), true, true, true));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Jint.Native.Function;
using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Descriptors;

namespace Jint.Native.FinalizationRegistry;

/// <summary>
/// https://tc39.es/ecma262/#sec-finalization-registry-constructor
/// </summary>
internal sealed class FinalizationRegistryConstructor : FunctionInstance, IConstructor
{
private static readonly JsString _functionName = new("FinalizationRegistry");

public FinalizationRegistryConstructor(
Engine engine,
Realm realm,
FunctionConstructor functionConstructor,
ObjectPrototype objectPrototype) : base(engine, realm, _functionName)
{
PrototypeObject = new FinalizationRegistryPrototype(engine, realm, this, objectPrototype);
_prototype = functionConstructor.PrototypeObject;
_prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
_length = new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.Configurable);
}

public FinalizationRegistryPrototype PrototypeObject { get; }

protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments)
{
return Construct(arguments, thisObject);
}

ObjectInstance IConstructor.Construct(JsValue[] arguments, JsValue newTarget) => Construct(arguments, newTarget);

private ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
{
if (newTarget.IsUndefined())
{
ExceptionHelper.ThrowTypeError(_realm);
}

var cleanupCallback = arguments.At(0);
if (cleanupCallback is not ICallable callable)
{
ExceptionHelper.ThrowTypeError(_realm, "cleanup must be callable");
return null;
}

var finalizationRegistry = OrdinaryCreateFromConstructor(
newTarget,
static intrinsics => intrinsics.FinalizationRegistry.PrototypeObject,
(engine, realm, state) => new FinalizationRegistryInstance(engine, realm, state!),
callable
);

return finalizationRegistry;
}
}
68 changes: 68 additions & 0 deletions Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Runtime.CompilerServices;
using Jint.Native.Object;
using Jint.Runtime;

namespace Jint.Native.FinalizationRegistry;

internal sealed record Cell(JsValue WeakRefTarget, JsValue HeldValue, ObjectInstance? UnregisterToken);

internal sealed class FinalizationRegistryInstance : ObjectInstance
{
private readonly Realm _realm;
private readonly JobCallback _callable;
private readonly ConditionalWeakTable<JsValue, List<Observer>> _cells = new();
private readonly Dictionary<JsValue, List<Observer>> _byToken = new();

public FinalizationRegistryInstance(Engine engine, Realm realm, ICallable cleanupCallback) : base(engine)
{
_realm = realm;
_callable = engine._host.MakeJobCallBack(cleanupCallback);
}

public void CleanupFinalizationRegistry(ICallable? callback)
{
}

public void AddCell(Cell cell)
{
var observer = new Observer(_callable);
var observerList = _cells.GetOrCreateValue(cell.WeakRefTarget);
observerList.Add(observer);

if (cell.UnregisterToken is not null)
{
if (!_byToken.TryGetValue(cell.UnregisterToken, out var list))
{
_byToken[cell.UnregisterToken] = list = new List<Observer>();
}
list.Add(observer);
}
}

public JsValue Remove(JsValue unregisterToken)
{
if (_byToken.TryGetValue(unregisterToken, out var list))
{
var any = list.Count > 0;
list.Clear();
return any;
}

return false;
}

private sealed class Observer
{
private readonly JobCallback _callable;

public Observer(JobCallback callable)
{
_callable = callable;
}

~Observer()
{
_callable.Callback.Call(Undefined);
}
}
}
120 changes: 120 additions & 0 deletions Jint/Native/FinalizationRegistry/FinalizationRegistryPrototype.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using Jint.Collections;
using Jint.Native.Object;
using Jint.Native.Symbol;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;

namespace Jint.Native.FinalizationRegistry;

/// <summary>
/// https://tc39.es/ecma262/#sec-properties-of-the-finalization-registry-prototype-object
/// </summary>
internal sealed class FinalizationRegistryPrototype : Prototype
{
private readonly FinalizationRegistryConstructor _constructor;

public FinalizationRegistryPrototype(
Engine engine,
Realm realm,
FinalizationRegistryConstructor constructor,
ObjectPrototype objectPrototype) : base(engine, realm)
{
_constructor = constructor;
_prototype = objectPrototype;
}

protected override void Initialize()
{
const PropertyFlag PropertyFlags = PropertyFlag.NonEnumerable;
var properties = new PropertyDictionary(4, checkExistingKeys: false)
{
[KnownKeys.Constructor] = new(_constructor, PropertyFlag.NonEnumerable),
["register"] = new(new ClrFunctionInstance(Engine, "register", Register, 2, PropertyFlag.Configurable), PropertyFlags),
["unregister"] = new(new ClrFunctionInstance(Engine, "unregister", Unregister, 1, PropertyFlag.Configurable), PropertyFlags),
["cleanupSome"] = new(new ClrFunctionInstance(Engine, "cleanupSome", CleanupSome, 0, PropertyFlag.Configurable), PropertyFlags),
};
SetProperties(properties);

var symbols = new SymbolDictionary(1) { [GlobalSymbolRegistry.ToStringTag] = new("FinalizationRegistry", PropertyFlag.Configurable) };
SetSymbols(symbols);
}

/// <summary>
/// https://tc39.es/ecma262/#sec-finalization-registry.prototype.register
/// </summary>
private JsValue Register(JsValue thisObj, JsValue[] arguments)
{
var finalizationRegistry = AssertFinalizationRegistryInstance(thisObj);

var target = arguments.At(0);
var heldValue = arguments.At(1);
var unregisterToken = arguments.At(2);

if (target is not ObjectInstance)
{
ExceptionHelper.ThrowTypeError(_realm, "target must be an object");
}

if (SameValue(target, heldValue))
{
ExceptionHelper.ThrowTypeError(_realm, "target and holdings must not be same");
}

if (unregisterToken is not ObjectInstance oi)
{
if (!unregisterToken.IsUndefined())
{
ExceptionHelper.ThrowTypeError(_realm, unregisterToken + " must be an object");
}

}
var cell = new Cell(target, heldValue, unregisterToken as ObjectInstance);
finalizationRegistry.AddCell(cell);
return Undefined;
}

/// <summary>
/// https://tc39.es/ecma262/#sec-finalization-registry.prototype.unregister
/// </summary>
private JsValue Unregister(JsValue thisObj, JsValue[] arguments)
{
var finalizationRegistry = AssertFinalizationRegistryInstance(thisObj);

var unregisterToken = arguments.At(0);

if (unregisterToken is not ObjectInstance oi)
{
ExceptionHelper.ThrowTypeError(_realm, unregisterToken + " must be an object");
}

return finalizationRegistry.Remove(unregisterToken);
}

private JsValue CleanupSome(JsValue thisObj, JsValue[] arguments)
{
var finalizationRegistry = AssertFinalizationRegistryInstance(thisObj);
var callback = arguments.At(0);

if (!callback.IsUndefined() && callback is not ICallable)
{
ExceptionHelper.ThrowTypeError(_realm, callback + " must be callable");
}

finalizationRegistry.CleanupFinalizationRegistry(callback as ICallable);

return Undefined;
}

private FinalizationRegistryInstance AssertFinalizationRegistryInstance(JsValue thisObj)
{
if (thisObj is not FinalizationRegistryInstance finalizationRegistryInstance)
{
ExceptionHelper.ThrowTypeError(_realm, "object must be a FinalizationRegistry");
return null;
}

return finalizationRegistryInstance;
}
}

2 changes: 1 addition & 1 deletion Jint/Native/Global/GlobalObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ protected override void Initialize()
["Date"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Date, propertyFlags),
["Error"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Error, propertyFlags),
["EvalError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.EvalError, propertyFlags),
["FinalizationRegistry"] = new LazyPropertyDescriptor(this, static state => Undefined, propertyFlags),
["FinalizationRegistry"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.FinalizationRegistry, propertyFlags),
["Float32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float32Array, propertyFlags),
["Float64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float64Array, propertyFlags),
["Function"] = new PropertyDescriptor(_realm.Intrinsics.Function, propertyFlags),
Expand Down
11 changes: 11 additions & 0 deletions Jint/Runtime/Host.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Jint.Native;
using Jint.Native.Function;
using Jint.Native.Global;
using Jint.Native.Object;
using Jint.Native.Promise;
Expand Down Expand Up @@ -190,5 +191,15 @@ public virtual void FinalizeImportMeta(ObjectInstance importMeta, ModuleRecord m
public virtual void InitializeShadowRealm(Realm realm)
{
}

/// <summary>
/// https://tc39.es/ecma262/#sec-hostmakejobcallback
/// </summary>
internal virtual JobCallback MakeJobCallBack(ICallable cleanupCallback)
{
return new JobCallback(cleanupCallback, null);
}
}
}

internal sealed record JobCallback(ICallable Callback, object? HostDefined);
5 changes: 5 additions & 0 deletions Jint/Runtime/Intrinsics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Jint.Native.DataView;
using Jint.Native.Date;
using Jint.Native.Error;
using Jint.Native.FinalizationRegistry;
using Jint.Native.Function;
using Jint.Native.Iterator;
using Jint.Native.Json;
Expand Down Expand Up @@ -81,6 +82,7 @@ public sealed class Intrinsics
private ArrayBufferConstructor? _arrayBufferConstructor;
private DataViewConstructor? _dataView;
private AsyncFunctionConstructor? _asyncFunction;
private FinalizationRegistryConstructor? _finalizationRegistry;

private IntrinsicTypedArrayConstructor? _typedArray;
private Int8ArrayConstructor? _int8Array;
Expand Down Expand Up @@ -116,6 +118,9 @@ internal Intrinsics(Engine engine, Realm realm)
public ObjectConstructor Object { get; }
public FunctionConstructor Function { get; }

internal FinalizationRegistryConstructor FinalizationRegistry =>
_finalizationRegistry ??= new FinalizationRegistryConstructor(_engine, _realm, Function, Object.PrototypeObject);

internal AsyncFunctionConstructor AsyncFunction =>
_asyncFunction ??= new AsyncFunctionConstructor(_engine, _realm, Function);

Expand Down
2 changes: 1 addition & 1 deletion Jint/Runtime/JavaScriptException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class JavaScriptException : JintException
}
else if (error is not null)
{
ret = TypeConverter.ToString(error);
ret = error.IsSymbol() ? error.ToString() : TypeConverter.ToString(error);
}

return ret;
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ The entire execution engine was rebuild with performance in mind, in many cases
#### ECMAScript 2016

-`Array.prototype.includes`
- `await`, `async`
- `await`, `async`
- ✔ Block-scoping of variables and functions
- ✔ Exponentiation operator `**`
- ✔ Destructuring patterns (of variables)
Expand Down Expand Up @@ -93,7 +93,7 @@ The entire execution engine was rebuild with performance in mind, in many cases
-`Promise.any`
-`String.prototype.replaceAll`
-`WeakRef`
- `FinalizationRegistry`
- `FinalizationRegistry`

#### ECMAScript 2022

Expand Down

0 comments on commit 7c5b87a

Please sign in to comment.