diff --git a/quickjs-atom.h b/quickjs-atom.h index cf92e68..ef7af29 100644 --- a/quickjs-atom.h +++ b/quickjs-atom.h @@ -210,6 +210,7 @@ DEF(Float32Array, "Float32Array") DEF(Float64Array, "Float64Array") DEF(DataView, "DataView") DEF(BigInt, "BigInt") +DEF(WeakRef, "WeakRef") DEF(Map, "Map") DEF(Set, "Set") /* Map + 1 */ DEF(WeakMap, "WeakMap") /* Map + 2 */ diff --git a/quickjs.c b/quickjs.c index eb677fb..0f9d2d0 100644 --- a/quickjs.c +++ b/quickjs.c @@ -175,6 +175,7 @@ enum { JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ + JS_CLASS_WEAK_REF, JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ }; @@ -419,8 +420,14 @@ typedef union JSFloat64Union { uint32_t u32[2]; } JSFloat64Union; +typedef struct JSWeakRefData { + JSValue target; + JSValue obj; +} JSWeakRefData; + typedef enum { JS_WEAK_REF_KIND_MAP, + JS_WEAK_REF_KIND_WEAK_REF, } JSWeakRefKindEnum; typedef struct JSWeakRefRecord { @@ -428,6 +435,7 @@ typedef struct JSWeakRefRecord { struct JSWeakRefRecord *next_weak_ref; union { struct JSMapRecord *map_record; + JSWeakRefData *weak_ref_data; } u; } JSWeakRefRecord; @@ -1009,6 +1017,8 @@ static void js_promise_mark(JSRuntime *rt, JSValueConst val, static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val); static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); +static void js_weakref_finalizer(JSRuntime *rt, JSValue val); + static JSValue JS_ToStringFree(JSContext *ctx, JSValue val); static int JS_ToBoolFree(JSContext *ctx, JSValue val); static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val); @@ -2004,6 +2014,7 @@ JSContext *JS_NewContext(JSRuntime *rt) JS_AddIntrinsicTypedArrays(ctx); JS_AddIntrinsicPromise(ctx); JS_AddIntrinsicBigInt(ctx); + JS_AddIntrinsicWeakRef(ctx); JS_AddPerformance(ctx); @@ -43880,6 +43891,7 @@ static void map_decref_record(JSRuntime *rt, JSMapRecord *mr) static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref) { JSWeakRefRecord *wr, *wr_next; + JSWeakRefData *wrd; JSMapRecord *mr; JSMapState *s; @@ -43887,7 +43899,7 @@ static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref) lists */ for(wr = *first_weak_ref; wr != NULL; wr = wr->next_weak_ref) { switch(wr->kind) { - case JS_WEAK_REF_KIND_MAP: { + case JS_WEAK_REF_KIND_MAP: mr = wr->u.map_record; s = mr->map; assert(s->is_weak); @@ -43895,7 +43907,10 @@ static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref) list_del(&mr->hash_link); list_del(&mr->link); break; - } + case JS_WEAK_REF_KIND_WEAK_REF: + wrd = wr->u.weak_ref_data; + wrd->target = JS_UNDEFINED; + break; default: abort(); } @@ -43906,12 +43921,16 @@ static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref) for(wr = *first_weak_ref; wr != NULL; wr = wr_next) { wr_next = wr->next_weak_ref; switch(wr->kind) { - case JS_WEAK_REF_KIND_MAP: { + case JS_WEAK_REF_KIND_MAP: mr = wr->u.map_record; JS_FreeValueRT(rt, mr->value); js_free_rt(rt, mr); break; - } + case JS_WEAK_REF_KIND_WEAK_REF: + wrd = wr->u.weak_ref_data; + JS_SetOpaque(wrd->obj, NULL); + js_free_rt(rt, wrd); + break; default: abort(); } @@ -50542,3 +50561,106 @@ void JS_AddPerformance(JSContext *ctx) JS_PROP_ENUMERABLE); JS_FreeValue(ctx, performance); } + +/* WeakRef */ + +static void js_weakref_finalizer(JSRuntime *rt, JSValue val) +{ + JSWeakRefData *wrd = JS_GetOpaque(val, JS_CLASS_WEAK_REF); + if (!wrd) + return; + + /* Delete weak ref */ + JSWeakRefRecord **pwr, *wr; + + pwr = get_first_weak_ref(wrd->target); + for(;;) { + wr = *pwr; + assert(wr != NULL); + if (wr->kind == JS_WEAK_REF_KIND_WEAK_REF && wr->u.weak_ref_data == wrd) + break; + pwr = &wr->next_weak_ref; + } + *pwr = wr->next_weak_ref; + js_free_rt(rt, wrd); + js_free_rt(rt, wr); +} + +static JSValue js_weakref_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) +{ + if (JS_IsUndefined(new_target)) + return JS_ThrowTypeError(ctx, "constructor requires 'new'"); + JSValue arg = argv[0]; + switch (JS_VALUE_GET_TAG(arg)) { + case JS_TAG_OBJECT: + break; + case JS_TAG_SYMBOL: { + // Per spec: prohibit symbols registered with Symbol.for() + JSAtomStruct *p = JS_VALUE_GET_PTR(arg); + if (p->atom_type != JS_ATOM_TYPE_GLOBAL_SYMBOL) + break; + // fallthru + } + default: + return JS_ThrowTypeError(ctx, "invalid target"); + } + // TODO(saghul): short-circuit if the refcount is 1? + JSValue obj = js_create_from_ctor(ctx, new_target, JS_CLASS_WEAK_REF); + if (JS_IsException(obj)) + return JS_EXCEPTION; + JSWeakRefData *wrd = js_malloc(ctx, sizeof(*wrd)); + if (!wrd) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + JSWeakRefRecord *wr, **pwr; + wr = js_malloc(ctx, sizeof(*wr)); + if (!wr) { + JS_FreeValue(ctx, obj); + js_free(ctx, wrd); + return JS_EXCEPTION; + } + wrd->target = arg; + wrd->obj = obj; + wr->kind = JS_WEAK_REF_KIND_WEAK_REF; + wr->u.weak_ref_data = wrd; + pwr = get_first_weak_ref(arg); + /* Add the weak reference */ + wr->next_weak_ref = *pwr; + *pwr = wr; + JS_SetOpaque(obj, wrd); + return obj; +} + +static JSValue js_weakref_deref(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSWeakRefData *wrd = JS_GetOpaque2(ctx, this_val, JS_CLASS_WEAK_REF); + if (!wrd) + return JS_EXCEPTION; + return JS_DupValue(ctx, wrd->target); +} + +static const JSCFunctionListEntry js_weakref_proto_funcs[] = { + JS_CFUNC_DEF("deref", 0, js_weakref_deref ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakRef", JS_PROP_CONFIGURABLE ), +}; + +static const JSClassShortDef js_weakref_class_def[] = { + { JS_ATOM_WeakRef, js_weakref_finalizer, NULL }, /* JS_CLASS_WEAK_REF */ +}; + +void JS_AddIntrinsicWeakRef(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + + if (!JS_IsRegisteredClass(rt, JS_CLASS_WEAK_REF)) { + init_class_range(rt, js_weakref_class_def, JS_CLASS_WEAK_REF, + countof(js_weakref_class_def)); + } + + ctx->class_proto[JS_CLASS_WEAK_REF] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_WEAK_REF], + js_weakref_proto_funcs, + countof(js_weakref_proto_funcs)); + JS_NewGlobalCConstructor(ctx, "WeakRef", js_weakref_constructor, 1, ctx->class_proto[JS_CLASS_WEAK_REF]); +} diff --git a/quickjs.h b/quickjs.h index f9b50b3..b018bad 100644 --- a/quickjs.h +++ b/quickjs.h @@ -364,6 +364,7 @@ JS_EXTERN void JS_AddIntrinsicMapSet(JSContext *ctx); JS_EXTERN void JS_AddIntrinsicTypedArrays(JSContext *ctx); JS_EXTERN void JS_AddIntrinsicPromise(JSContext *ctx); JS_EXTERN void JS_AddIntrinsicBigInt(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicWeakRef(JSContext *ctx); JS_EXTERN void JS_AddPerformance(JSContext *ctx); /* Only used for running 262 tests. TODO(saghul) add build time flag. */ diff --git a/test262.conf b/test262.conf index d1ff09a..e01642c 100644 --- a/test262.conf +++ b/test262.conf @@ -204,7 +204,7 @@ Uint32Array Uint8Array Uint8ClampedArray WeakMap -WeakRef=skip +WeakRef WeakSet well-formed-json-stringify