diff --git a/packages/core/src/emnapi/index.d.ts b/packages/core/src/emnapi/index.d.ts index a626d404..887dbf42 100644 --- a/packages/core/src/emnapi/index.d.ts +++ b/packages/core/src/emnapi/index.d.ts @@ -1,10 +1,10 @@ -import type { Context } from '@emnapi/runtime' +import type { Context, ReferenceOwnership } from '@emnapi/runtime' import type { ThreadManager, ThreadManagerOptionsMain, MainThreadBaseOptions } from '@emnapi/wasi-threads' /** @public */ export declare interface PointerInfo { address: number - ownership: 0 | 1 + ownership: ReferenceOwnership runtimeAllocated: 0 | 1 } diff --git a/packages/emnapi/src/core/async-work.ts b/packages/emnapi/src/core/async-work.ts index 1cb77f2a..fc62c9f0 100644 --- a/packages/emnapi/src/core/async-work.ts +++ b/packages/emnapi/src/core/async-work.ts @@ -277,7 +277,7 @@ export var napi_create_async_work = singleThreadAsyncWork if (!aw) return envObject.setLastError(napi_status.napi_generic_failure) new Uint8Array(wasmMemory.buffer).subarray(aw, aw + sizeofAW).fill(0) const s = envObject.ensureHandleId(resourceObject) - const resourceRef = emnapiCtx.createReference(envObject, s, 1, Ownership.kUserland as any) + const resourceRef = emnapiCtx.createReference(envObject, s, 1, ReferenceOwnership.kUserland as any) // eslint-disable-next-line @typescript-eslint/no-unused-vars const resource_ = resourceRef.id makeSetValue('aw', 0, 'resource_', '*') diff --git a/packages/emnapi/src/emnapi.ts b/packages/emnapi/src/emnapi.ts index e59986a8..5d752e85 100644 --- a/packages/emnapi/src/emnapi.ts +++ b/packages/emnapi/src/emnapi.ts @@ -45,46 +45,46 @@ export function emnapi_create_memory_view ( let viewDescriptor: MemoryViewDescriptor switch (typedarray_type) { case emnapi_memory_view_type.emnapi_int8_array: - viewDescriptor = { Ctor: Int8Array, address: external_data, length: byte_length, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: Int8Array, address: external_data, length: byte_length, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_uint8_array: - viewDescriptor = { Ctor: Uint8Array, address: external_data, length: byte_length, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: Uint8Array, address: external_data, length: byte_length, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_uint8_clamped_array: - viewDescriptor = { Ctor: Uint8ClampedArray, address: external_data, length: byte_length, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: Uint8ClampedArray, address: external_data, length: byte_length, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_int16_array: - viewDescriptor = { Ctor: Int16Array, address: external_data, length: byte_length >> 1, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: Int16Array, address: external_data, length: byte_length >> 1, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_uint16_array: - viewDescriptor = { Ctor: Uint16Array, address: external_data, length: byte_length >> 1, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: Uint16Array, address: external_data, length: byte_length >> 1, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_int32_array: - viewDescriptor = { Ctor: Int32Array, address: external_data, length: byte_length >> 2, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: Int32Array, address: external_data, length: byte_length >> 2, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_uint32_array: - viewDescriptor = { Ctor: Uint32Array, address: external_data, length: byte_length >> 2, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: Uint32Array, address: external_data, length: byte_length >> 2, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_float32_array: - viewDescriptor = { Ctor: Float32Array, address: external_data, length: byte_length >> 2, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: Float32Array, address: external_data, length: byte_length >> 2, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_float64_array: - viewDescriptor = { Ctor: Float64Array, address: external_data, length: byte_length >> 3, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: Float64Array, address: external_data, length: byte_length >> 3, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_bigint64_array: - viewDescriptor = { Ctor: BigInt64Array, address: external_data, length: byte_length >> 3, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: BigInt64Array, address: external_data, length: byte_length >> 3, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_biguint64_array: - viewDescriptor = { Ctor: BigUint64Array, address: external_data, length: byte_length >> 3, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: BigUint64Array, address: external_data, length: byte_length >> 3, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_data_view: - viewDescriptor = { Ctor: DataView, address: external_data, length: byte_length, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: DataView, address: external_data, length: byte_length, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break case emnapi_memory_view_type.emnapi_buffer: { if (!emnapiCtx.feature.Buffer) { throw emnapiCtx.createNotSupportBufferError('emnapi_create_memory_view', '') } - viewDescriptor = { Ctor: emnapiCtx.feature.Buffer!, address: external_data, length: byte_length, ownership: Ownership.kUserland, runtimeAllocated: 0 } + viewDescriptor = { Ctor: emnapiCtx.feature.Buffer!, address: external_data, length: byte_length, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 } break } default: return envObject.setLastError(napi_status.napi_invalid_arg) diff --git a/packages/emnapi/src/internal.ts b/packages/emnapi/src/internal.ts index 3e74dc8d..be659f47 100644 --- a/packages/emnapi/src/internal.ts +++ b/packages/emnapi/src/internal.ts @@ -143,12 +143,14 @@ export function emnapiWrap (env: napi_env, js_object: napi_value, native_object: let reference: Reference if (result) { if (!finalize_cb) return envObject.setLastError(napi_status.napi_invalid_arg) - reference = emnapiCtx.createReference(envObject, handle.id, 0, Ownership.kUserland as any, finalize_cb, native_object, finalize_hint) + reference = emnapiCtx.createReferenceWithFinalizer(envObject, handle.id, 0, ReferenceOwnership.kUserland as any, finalize_cb, native_object, finalize_hint) from64('result') referenceId = reference.id makeSetValue('result', 0, 'referenceId', '*') + } else if (finalize_cb) { + reference = emnapiCtx.createReferenceWithFinalizer(envObject, handle.id, 0, ReferenceOwnership.kRuntime as any, finalize_cb, native_object, finalize_hint) } else { - reference = emnapiCtx.createReference(envObject, handle.id, 0, Ownership.kRuntime as any, finalize_cb, native_object, !finalize_cb ? finalize_cb : finalize_hint) + reference = emnapiCtx.createReferenceWithData(envObject, handle.id, 0, ReferenceOwnership.kRuntime as any, native_object) } envObject.getObjectBinding(handle.value).wrapped = reference.id @@ -181,7 +183,7 @@ export function emnapiUnwrap (env: napi_env, js_object: napi_value, result: void } if (action === UnwrapAction.RemoveWrap) { binding.wrapped = 0 - if (ref.ownership() === Ownership.kUserland) { + if ((ref.ownership() as unknown as ReferenceOwnership) === ReferenceOwnership.kUserland) { // When the wrap is been removed, the finalizer should be reset. ref.resetFinalizer() } else { diff --git a/packages/emnapi/src/life.ts b/packages/emnapi/src/life.ts index 4916475b..3bb3840b 100644 --- a/packages/emnapi/src/life.ts +++ b/packages/emnapi/src/life.ts @@ -86,7 +86,7 @@ export function napi_create_reference ( } } // eslint-disable-next-line @typescript-eslint/no-unused-vars - const ref = emnapiCtx.createReference(envObject, handle.id, initial_refcount >>> 0, Ownership.kUserland as any) + const ref = emnapiCtx.createReference(envObject, handle.id, initial_refcount >>> 0, ReferenceOwnership.kUserland as any) from64('result') makeSetValue('result', 0, 'ref.id', '*') return envObject.clearLastError() @@ -129,7 +129,7 @@ export function napi_reference_unref ( const envObject: Env = $CHECK_ENV_NOT_IN_GC!(env) $CHECK_ARG!(envObject, ref) const reference = emnapiCtx.refStore.get(ref)! - const refcount = reference.refCount() + const refcount = reference.refcount() if (refcount === 0) { return envObject.setLastError(napi_status.napi_generic_failure) @@ -154,7 +154,7 @@ export function napi_get_reference_value ( $CHECK_ARG!(envObject, result) const reference = emnapiCtx.refStore.get(ref)! // eslint-disable-next-line @typescript-eslint/no-unused-vars - const handleId = reference.get() + const handleId = reference.get(envObject) from64('result') makeSetValue('result', 0, 'handleId', '*') return envObject.clearLastError() diff --git a/packages/emnapi/src/memory.ts b/packages/emnapi/src/memory.ts index 75df0a2f..b279d7e2 100644 --- a/packages/emnapi/src/memory.ts +++ b/packages/emnapi/src/memory.ts @@ -19,7 +19,7 @@ export type ViewConstuctor = export interface ArrayBufferPointer { address: void_p - ownership: Ownership + ownership: ReferenceOwnership runtimeAllocated: 0 | 1 } @@ -75,7 +75,7 @@ export const emnapiExternalMemory: { getArrayBufferPointer: function (arrayBuffer: ArrayBuffer, shouldCopy: boolean): ArrayBufferPointer { const info: ArrayBufferPointer = { address: 0, - ownership: Ownership.kRuntime, + ownership: ReferenceOwnership.kRuntime, runtimeAllocated: 0 } if (arrayBuffer === wasmMemory.buffer) { @@ -89,7 +89,7 @@ export const emnapiExternalMemory: { cachedInfo.address = 0 return cachedInfo } - if (shouldCopy && cachedInfo.ownership === Ownership.kRuntime && cachedInfo.runtimeAllocated === 1) { + if (shouldCopy && cachedInfo.ownership === ReferenceOwnership.kRuntime && cachedInfo.runtimeAllocated === 1) { new Uint8Array(wasmMemory.buffer).set(new Uint8Array(arrayBuffer), cachedInfo.address) } return cachedInfo @@ -108,7 +108,7 @@ export const emnapiExternalMemory: { new Uint8Array(wasmMemory.buffer).set(new Uint8Array(arrayBuffer), pointer) info.address = pointer - info.ownership = emnapiExternalMemory.registry ? Ownership.kRuntime : Ownership.kUserland + info.ownership = emnapiExternalMemory.registry ? ReferenceOwnership.kRuntime : ReferenceOwnership.kUserland info.runtimeAllocated = 1 emnapiExternalMemory.table.set(arrayBuffer, info) @@ -123,7 +123,7 @@ export const emnapiExternalMemory: { Ctor: view.constructor as any, address: view.byteOffset, length: view instanceof DataView ? view.byteLength : (view as any).length, - ownership: Ownership.kUserland, + ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 }) } @@ -156,7 +156,7 @@ export const emnapiExternalMemory: { const { address, ownership, runtimeAllocated } = emnapiExternalMemory.wasmMemoryViewTable.get(view)! return { address, ownership, runtimeAllocated, view } } - return { address: view.byteOffset, ownership: Ownership.kUserland, runtimeAllocated: 0, view } + return { address: view.byteOffset, ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0, view } } const { address, ownership, runtimeAllocated } = emnapiExternalMemory.getArrayBufferPointer(view.buffer, shouldCopy) diff --git a/packages/emnapi/src/threadsafe-function.ts b/packages/emnapi/src/threadsafe-function.ts index 6e53b554..e71d077d 100644 --- a/packages/emnapi/src/threadsafe-function.ts +++ b/packages/emnapi/src/threadsafe-function.ts @@ -682,7 +682,7 @@ export function napi_create_threadsafe_function ( return envObject.setLastError(napi_status.napi_invalid_arg) } // eslint-disable-next-line @typescript-eslint/no-unused-vars - ref = emnapiCtx.createReference(envObject, func, 1, Ownership.kUserland as any).id + ref = emnapiCtx.createReference(envObject, func, 1, ReferenceOwnership.kUserland as any).id } let asyncResourceObject: any @@ -709,7 +709,7 @@ export function napi_create_threadsafe_function ( const tsfn = _malloc(to64('sizeofTSFN')) if (!tsfn) return envObject.setLastError(napi_status.napi_generic_failure) new Uint8Array(wasmMemory.buffer).subarray(tsfn, tsfn + sizeofTSFN).fill(0) - const resourceRef = emnapiCtx.createReference(envObject, resource, 1, Ownership.kUserland as any) + const resourceRef = emnapiCtx.createReference(envObject, resource, 1, ReferenceOwnership.kUserland as any) // eslint-disable-next-line @typescript-eslint/no-unused-vars const resource_ = resourceRef.id makeSetValue('tsfn', 0, 'resource_', '*') diff --git a/packages/emnapi/src/typings/enum.d.ts b/packages/emnapi/src/typings/enum.d.ts index 6a25fbae..98ebd856 100644 --- a/packages/emnapi/src/typings/enum.d.ts +++ b/packages/emnapi/src/typings/enum.d.ts @@ -1,4 +1,13 @@ -declare const enum UnwrapAction { - KeepWrap, - RemoveWrap +import type { ReferenceOwnership as RuntimeReferenceOwnership } from '@emnapi/runtime' + +declare global { + export const enum UnwrapAction { + KeepWrap, + RemoveWrap + } + + export const enum ReferenceOwnership { + kRuntime = RuntimeReferenceOwnership.kRuntime, + kUserland = RuntimeReferenceOwnership.kUserland + } } diff --git a/packages/emnapi/src/value/create.ts b/packages/emnapi/src/value/create.ts index d3e6bd04..520bb59f 100644 --- a/packages/emnapi/src/value/create.ts +++ b/packages/emnapi/src/value/create.ts @@ -98,7 +98,7 @@ export function napi_create_external (env: napi_env, data: void_p, finalize_cb: } const externalHandle = emnapiCtx.getCurrentScope()!.addExternal(envObject, data) if (finalize_cb) { - emnapiCtx.createReference(envObject, externalHandle.id, 0, Ownership.kRuntime as any, finalize_cb, data, finalize_hint) + emnapiCtx.createReferenceWithFinalizer(envObject, externalHandle.id, 0, ReferenceOwnership.kRuntime as any, finalize_cb, data, finalize_hint) } from64('result') value = externalHandle.id @@ -151,7 +151,7 @@ export function napi_create_external_arraybuffer ( u8arr.set(new Uint8Array(wasmMemory.buffer).subarray(external_data, external_data + byte_length)) emnapiExternalMemory.table.set(arrayBuffer, { address: external_data, - ownership: Ownership.kUserland, + ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 }) } @@ -262,7 +262,7 @@ export function napi_create_typedarray ( Ctor: Type as any, address: byte_offset, length, - ownership: Ownership.kUserland, + ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 }) } @@ -343,7 +343,7 @@ export function napi_create_buffer ( Ctor: Buffer, address: pointer, length: size, - ownership: emnapiExternalMemory.registry ? Ownership.kRuntime : Ownership.kUserland, + ownership: emnapiExternalMemory.registry ? ReferenceOwnership.kRuntime : ReferenceOwnership.kUserland, runtimeAllocated: 1 } emnapiExternalMemory.wasmMemoryViewTable.set(buffer, viewDescriptor) @@ -451,7 +451,7 @@ export function napi_create_dataview ( Ctor: DataView, address: byte_offset, length: byte_length, - ownership: Ownership.kUserland, + ownership: ReferenceOwnership.kUserland, runtimeAllocated: 0 }) } diff --git a/packages/emnapi/src/wrap.ts b/packages/emnapi/src/wrap.ts index a28a38b7..0cf98922 100644 --- a/packages/emnapi/src/wrap.ts +++ b/packages/emnapi/src/wrap.ts @@ -194,11 +194,11 @@ export function napi_add_finalizer (env: napi_env, js_object: napi_value, finali } const handle = handleResult.handle! - const ownership: Ownership = !result ? Ownership.kRuntime : Ownership.kUserland + const ownership: ReferenceOwnership = !result ? ReferenceOwnership.kRuntime : ReferenceOwnership.kUserland from64('finalize_data') from64('finalize_cb') from64('finalize_hint') - const reference = emnapiCtx.createReference(envObject, handle.id, 0, ownership as any, finalize_cb, finalize_data, finalize_hint) + const reference = emnapiCtx.createReferenceWithFinalizer(envObject, handle.id, 0, ownership as any, finalize_cb, finalize_data, finalize_hint) if (result) { from64('result') // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/runtime/src/Context.ts b/packages/runtime/src/Context.ts index dc56ae09..129c1b18 100644 --- a/packages/runtime/src/Context.ts +++ b/packages/runtime/src/Context.ts @@ -21,7 +21,7 @@ import { } from './util' import { CallbackInfoStack } from './CallbackInfo' import { NotSupportWeakRefError, NotSupportBufferError } from './errors' -import { Reference } from './Reference' +import { Reference, ReferenceWithData, ReferenceWithFinalizer, type ReferenceOwnership } from './Reference' import { type IDeferrdValue, Deferred } from './Deferred' import { Store } from './Store' import { TrackedFinalizer } from './TrackedFinalizer' @@ -164,12 +164,42 @@ export class Context { envObject: Env, handle_id: napi_value, initialRefcount: uint32_t, - ownership: 0 | 1, + ownership: ReferenceOwnership + ): Reference { + return Reference.create( + envObject, + handle_id, + initialRefcount, + ownership + ) + } + + public createReferenceWithData ( + envObject: Env, + handle_id: napi_value, + initialRefcount: uint32_t, + ownership: ReferenceOwnership, + data: void_p + ): Reference { + return ReferenceWithData.create( + envObject, + handle_id, + initialRefcount, + ownership, + data + ) + } + + public createReferenceWithFinalizer ( + envObject: Env, + handle_id: napi_value, + initialRefcount: uint32_t, + ownership: ReferenceOwnership, finalize_callback: napi_finalize = 0, finalize_data: void_p = 0, finalize_hint: void_p = 0 ): Reference { - return Reference.create( + return ReferenceWithFinalizer.create( envObject, handle_id, initialRefcount, diff --git a/packages/runtime/src/Finalizer.ts b/packages/runtime/src/Finalizer.ts index 262b585d..a216f4af 100644 --- a/packages/runtime/src/Finalizer.ts +++ b/packages/runtime/src/Finalizer.ts @@ -1,24 +1,49 @@ import type { Env } from './env' export class Finalizer { + private _makeDynCall_vppp: (cb: Ptr) => (a: Ptr, b: Ptr, c: Ptr) => void + public constructor ( public envObject: Env, - protected _finalizeCallback: napi_finalize = 0, - protected _finalizeData: void_p = 0, - protected _finalizeHint: void_p = 0 - ) {} + private _finalizeCallback: napi_finalize = 0, + private _finalizeData: void_p = 0, + private _finalizeHint: void_p = 0 + ) { + this._makeDynCall_vppp = envObject.makeDynCall_vppp + } public callback (): napi_finalize { return this._finalizeCallback } public data (): void_p { return this._finalizeData } public hint (): void_p { return this._finalizeHint } + public resetEnv (): void { + this.envObject = undefined! + } + public resetFinalizer (): void { this._finalizeCallback = 0 this._finalizeData = 0 this._finalizeHint = 0 } + public callFinalizer (): void { + const finalize_callback = this._finalizeCallback + const finalize_data = this._finalizeData + const finalize_hint = this._finalizeHint + this.resetFinalizer() + + if (!finalize_callback) return + + const fini = Number(finalize_callback) + if (!this.envObject) { + this._makeDynCall_vppp(fini)(0, finalize_data, finalize_hint) + } else { + this.envObject.callFinalizer(fini, finalize_data, finalize_hint) + } + } + public dispose (): void { this.envObject = undefined! + this._makeDynCall_vppp = undefined! } } diff --git a/packages/runtime/src/RefBase.ts b/packages/runtime/src/RefBase.ts deleted file mode 100644 index 29367b03..00000000 --- a/packages/runtime/src/RefBase.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Env } from './env' -import { TrackedFinalizer } from './TrackedFinalizer' - -export class RefBase extends TrackedFinalizer { - private _refcount: uint32_t - private readonly _ownership: Ownership - - constructor ( - envObject: Env, - initial_refcount: uint32_t, - ownership: number, - finalize_callback: napi_finalize, - finalize_data: void_p, - finalize_hint: void_p - ) { - super(envObject, finalize_callback, finalize_data, finalize_hint) - this._refcount = initial_refcount - this._ownership = ownership - } - - public data (): void_p { - return this._finalizeData - } - - public ref (): uint32_t { - return ++this._refcount - } - - public unref (): uint32_t { - if (this._refcount === 0) { - return 0 - } - return --this._refcount - } - - public refCount (): uint32_t { - return this._refcount - } - - public ownership (): number { - return this._ownership - } - - public finalize (): void { - this.finalizeCore(this._ownership === Ownership.kRuntime) - } -} diff --git a/packages/runtime/src/RefTracker.ts b/packages/runtime/src/RefTracker.ts index 45f40fb3..f144447b 100644 --- a/packages/runtime/src/RefTracker.ts +++ b/packages/runtime/src/RefTracker.ts @@ -1,4 +1,7 @@ export class RefTracker { + /** @virtual */ + public dispose (): void {} + /** @virtual */ public finalize (): void {} diff --git a/packages/runtime/src/Reference.ts b/packages/runtime/src/Reference.ts index 4967a4ee..24dc1c62 100644 --- a/packages/runtime/src/Reference.ts +++ b/packages/runtime/src/Reference.ts @@ -1,55 +1,65 @@ import type { IStoreValue } from './Store' import type { Env } from './env' -import { RefBase } from './RefBase' import { Persistent } from './Persistent' import type { Handle } from './Handle' +import { RefTracker } from './RefTracker' +import { Finalizer } from './Finalizer' -function weakCallback (ref: Reference): void { - ref.persistent.reset() - ref.envObject.invokeFinalizerFromGC(ref) +export enum ReferenceOwnership { + kRuntime, + kUserland } function canBeHeldWeakly (value: Handle): boolean { return value.isObject() || value.isFunction() || value.isSymbol() } -export class Reference extends RefBase implements IStoreValue { +export class Reference extends RefTracker implements IStoreValue { + private static weakCallback (ref: Reference): void { + ref.persistent.reset() + ref.invokeFinalizerFromGC() + } + public id: number - private canBeWeak!: boolean + public envObject!: Env + + private readonly canBeWeak!: boolean + private _refcount: number + private readonly _ownership: ReferenceOwnership + public persistent!: Persistent public static create ( envObject: Env, handle_id: napi_value, initialRefcount: uint32_t, - ownership: 0 | 1, - finalize_callback: napi_finalize = 0, - finalize_data: void_p = 0, - finalize_hint: void_p = 0 + ownership: ReferenceOwnership, + _unused1?: void_p, + _unused2?: void_p, + _unused3?: void_p ): Reference { - const handle = envObject.ctx.handleStore.get(handle_id)! - const ref = new Reference(envObject, initialRefcount, ownership, finalize_callback, finalize_data, finalize_hint) + const ref = new Reference(envObject, handle_id, initialRefcount, ownership) envObject.ctx.refStore.add(ref) - ref.canBeWeak = canBeHeldWeakly(handle) - ref.persistent = new Persistent(handle.value) - - if (initialRefcount === 0) { - ref._setWeak() - } + ref.link(envObject.reflist) return ref } - public persistent!: Persistent - - private constructor ( + protected constructor ( envObject: Env, + handle_id: napi_value, initialRefcount: uint32_t, - ownership: Ownership, - finalize_callback: napi_finalize = 0, - finalize_data: void_p = 0, - finalize_hint: void_p = 0 + ownership: ReferenceOwnership ) { - super(envObject, initialRefcount >>> 0, ownership, finalize_callback, finalize_data, finalize_hint) + super() + this.envObject = envObject + this._refcount = initialRefcount + this._ownership = ownership + const handle = envObject.ctx.handleStore.get(handle_id)! + this.canBeWeak = canBeHeldWeakly(handle) + this.persistent = new Persistent(handle.value) this.id = 0 + if (initialRefcount === 0) { + this._setWeak() + } } public ref (): number { @@ -57,40 +67,54 @@ export class Reference extends RefBase implements IStoreValue { return 0 } - const count = super.ref() - - if (count === 1 && this.canBeWeak) { + if (++this._refcount === 1 && this.canBeWeak) { this.persistent.clearWeak() } - return count + return this._refcount } public unref (): number { - if (this.persistent.isEmpty()) { + if (this.persistent.isEmpty() || this._refcount === 0) { return 0 } - const oldRefcount = this.refCount() - const refcount = super.unref() - if (oldRefcount === 1 && refcount === 0) { + if (--this._refcount === 0) { this._setWeak() } - return refcount + return this._refcount } - public get (): napi_value { + public get (envObject = this.envObject): napi_value { if (this.persistent.isEmpty()) { return 0 } const obj = this.persistent.deref() - const handle = this.envObject.ensureHandle(obj) + const handle = envObject.ensureHandle(obj) return handle.id } + /** @virtual */ + public resetFinalizer (): void {} + + /** @virtual */ + public data (): void_p { return 0 } + + public refcount (): number { return this._refcount } + + public ownership (): ReferenceOwnership { return this._ownership } + + /** @virtual */ + protected callUserFinalizer (): void {} + + /** @virtual */ + protected invokeFinalizerFromGC (): void { + this.finalize() + } + private _setWeak (): void { if (this.canBeWeak) { - this.persistent.setWeak(this, weakCallback) + this.persistent.setWeak(this, Reference.weakCallback) } else { this.persistent.reset() } @@ -98,14 +122,106 @@ export class Reference extends RefBase implements IStoreValue { public override finalize (): void { this.persistent.reset() - super.finalize() + const deleteMe = this._ownership === ReferenceOwnership.kRuntime + this.unlink() + this.callUserFinalizer() + if (deleteMe) { + this.dispose() + } } public override dispose (): void { if (this.id === 0) return + this.unlink() this.persistent.reset() this.envObject.ctx.refStore.remove(this.id) super.dispose() + this.envObject = undefined! this.id = 0 } } + +export class ReferenceWithData extends Reference { + public static override create ( + envObject: Env, + value: napi_value, + initialRefcount: uint32_t, + ownership: ReferenceOwnership, + data: void_p + ): ReferenceWithData { + const reference = new ReferenceWithData(envObject, value, initialRefcount, ownership, data) + envObject.ctx.refStore.add(reference) + reference.link(envObject.reflist) + return reference + } + + private constructor ( + envObject: Env, + value: napi_value, + initialRefcount: uint32_t, + ownership: ReferenceOwnership, + private readonly _data: void_p + ) { + super(envObject, value, initialRefcount, ownership) + } + + public data (): void_p { + return this._data + } +} + +export class ReferenceWithFinalizer extends Reference { + private _finalizer: Finalizer + + public static override create ( + envObject: Env, + value: napi_value, + initialRefcount: uint32_t, + ownership: ReferenceOwnership, + finalize_callback: napi_finalize, + finalize_data: void_p, + finalize_hint: void_p + ): ReferenceWithFinalizer { + const reference = new ReferenceWithFinalizer(envObject, value, initialRefcount, ownership, finalize_callback, finalize_data, finalize_hint) + envObject.ctx.refStore.add(reference) + reference.link(envObject.finalizing_reflist) + return reference + } + + private constructor ( + envObject: Env, + value: napi_value, + initialRefcount: uint32_t, + ownership: ReferenceOwnership, + finalize_callback: napi_finalize, + finalize_data: void_p, + finalize_hint: void_p + ) { + super(envObject, value, initialRefcount, ownership) + this._finalizer = new Finalizer(envObject, finalize_callback, finalize_data, finalize_hint) + } + + public override resetFinalizer (): void { + this._finalizer.resetFinalizer() + } + + public override data (): void_p { + return this._finalizer.data() + } + + protected override callUserFinalizer (): void { + this._finalizer.callFinalizer() + } + + protected override invokeFinalizerFromGC (): void { + this._finalizer.envObject.invokeFinalizerFromGC(this) + } + + public override dispose (): void { + if (!this._finalizer) return + this._finalizer.envObject.dequeueFinalizer(this) + this._finalizer.dispose() + super.dispose() + this._finalizer = undefined! + } +} diff --git a/packages/runtime/src/TrackedFinalizer.ts b/packages/runtime/src/TrackedFinalizer.ts index 090190b1..2be6f95d 100644 --- a/packages/runtime/src/TrackedFinalizer.ts +++ b/packages/runtime/src/TrackedFinalizer.ts @@ -2,20 +2,8 @@ import type { Env } from './env' import { Finalizer } from './Finalizer' import { RefTracker } from './RefTracker' -export interface TrackedFinalizer extends Finalizer, RefTracker {} - -export class TrackedFinalizer extends Finalizer { - public static finalizeAll (list: RefTracker): void { - RefTracker.finalizeAll(list) - } - - public link (list: RefTracker): void { - RefTracker.prototype.link.call(this, list) - } - - public unlink (): void { - RefTracker.prototype.unlink.call(this) - } +export class TrackedFinalizer extends RefTracker { + private _finalizer: Finalizer public static create ( envObject: Env, @@ -23,53 +11,47 @@ export class TrackedFinalizer extends Finalizer { finalize_data: void_p, finalize_hint: void_p ): TrackedFinalizer { - return new TrackedFinalizer(envObject, finalize_callback, finalize_data, finalize_hint) + const finalizer = new TrackedFinalizer(envObject, finalize_callback, finalize_data, finalize_hint) + finalizer.link(envObject.finalizing_reflist) + return finalizer } - protected constructor ( + private constructor ( envObject: Env, finalize_callback: napi_finalize, finalize_data: void_p, finalize_hint: void_p ) { - super(envObject, finalize_callback, finalize_data, finalize_hint) - ;(this as any)._next = null - ;(this as any)._prev = null - this.link(!finalize_callback ? envObject.reflist : envObject.finalizing_reflist) + super() + this._finalizer = new Finalizer(envObject, finalize_callback, finalize_data, finalize_hint) } - public dispose (): void { - this.unlink() - this.envObject.dequeueFinalizer(this) - super.dispose() + public data (): void_p { + return this._finalizer.data() } - public finalize (): void { - this.finalizeCore(true) + public override dispose (): void { + if (!this._finalizer) return + this.unlink() + this._finalizer.envObject.dequeueFinalizer(this) + this._finalizer.dispose() + this._finalizer = undefined! + super.dispose() } - protected finalizeCore (deleteMe: boolean): void { - const finalize_callback = this._finalizeCallback - const finalize_data = this._finalizeData - const finalize_hint = this._finalizeHint - this.resetFinalizer() - + public override finalize (): void { this.unlink() let error: any let caught = false - if (finalize_callback) { - const fini = Number(finalize_callback) - try { - this.envObject.callFinalizer(fini, finalize_data, finalize_hint) - } catch (err) { - caught = true - error = err - } - } - if (deleteMe) { - this.dispose() + + try { + this._finalizer.callFinalizer() + } catch (err) { + caught = true + error = err } + this.dispose() if (caught) { throw error } diff --git a/packages/runtime/src/env.ts b/packages/runtime/src/env.ts index df3411ad..29a99096 100644 --- a/packages/runtime/src/env.ts +++ b/packages/runtime/src/env.ts @@ -9,7 +9,7 @@ import { NODE_API_DEFAULT_MODULE_API_VERSION } from './util' import { RefTracker } from './RefTracker' -import { RefBase } from './RefBase' +import { TrackedFinalizer } from './TrackedFinalizer' function throwNodeApiVersionError (moduleName: string, moduleApiVersion: number): never { const errorMessage = `${ @@ -32,12 +32,12 @@ export interface IReferenceBinding { data: void_p } -export class Env implements IStoreValue { +export abstract class Env implements IStoreValue { public id: number public openHandleScopes: number = 0 - public instanceData: RefBase | null = null + public instanceData: TrackedFinalizer | null = null public tryCatch = new TryCatch() @@ -131,16 +131,7 @@ export class Env implements IStoreValue { } /** @virtual */ - public callFinalizer (cb: napi_finalize, data: void_p, hint: void_p): void { - const f = this.makeDynCall_vppp(cb) - const env: napi_env = this.id - const scope = this.ctx.openScope(this) - try { - this.callIntoModule(() => { f(env, data, hint) }) - } finally { - this.ctx.closeScope(this, scope) - } - } + public abstract callFinalizer (cb: napi_finalize, data: void_p, hint: void_p): void public invokeFinalizerFromGC (finalizer: RefTracker): void { if (this.moduleApiVersion !== NAPI_VERSION_EXPERIMENTAL) { @@ -186,8 +177,8 @@ export class Env implements IStoreValue { /** @virtual */ public deleteMe (): void { - RefBase.finalizeAll(this.finalizing_reflist) - RefBase.finalizeAll(this.reflist) + RefTracker.finalizeAll(this.finalizing_reflist) + RefTracker.finalizeAll(this.reflist) this.tryCatch.extractException() this.ctx.envStore.remove(this.id) @@ -196,6 +187,9 @@ export class Env implements IStoreValue { public dispose (): void { if (this.id === 0) return this.deleteMe() + + this.finalizing_reflist.dispose() + this.reflist.dispose() this.id = 0 } @@ -222,7 +216,7 @@ export class Env implements IStoreValue { if (this.instanceData) { this.instanceData.dispose() } - this.instanceData = new RefBase(this, 0, Ownership.kRuntime, finalize_cb, data, finalize_hint) + this.instanceData = TrackedFinalizer.create(this, finalize_cb, data, finalize_hint) } public getInstanceData (): number { diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index b3b040e7..63030c48 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -7,9 +7,8 @@ export { Finalizer } from './Finalizer' export { TrackedFinalizer } from './TrackedFinalizer' export { Handle, ConstHandle, HandleStore } from './Handle' export { HandleScope } from './HandleScope' -export { RefBase } from './RefBase' export { Persistent } from './Persistent' -export { Reference } from './Reference' +export { Reference, ReferenceWithData, ReferenceWithFinalizer, ReferenceOwnership } from './Reference' export { RefTracker } from './RefTracker' export { ScopeStore } from './ScopeStore' export { Store, type IStoreValue } from './Store' diff --git a/packages/runtime/src/typings/common.d.ts b/packages/runtime/src/typings/common.d.ts index 3b79c0cf..d382e3ad 100644 --- a/packages/runtime/src/typings/common.d.ts +++ b/packages/runtime/src/typings/common.d.ts @@ -21,11 +21,6 @@ declare const enum GlobalHandle { GLOBAL } -declare const enum Ownership { - kRuntime, - kUserland -} - declare const enum Version { NODE_API_SUPPORTED_VERSION_MIN = 1, NODE_API_DEFAULT_MODULE_API_VERSION = 8,