From a7c3cf586528671047696dc33509186bf1e526f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= <s@saghul.net>
Date: Mon, 9 Sep 2024 22:40:40 +0200
Subject: [PATCH] Use an integer as the timer handle

Port of
https://github.com/bellard/quickjs/commit/9e561d5c2e986e535484c26385d6e1b8d08a238f
but adapted.
---
 quickjs-libc.c | 108 +++++++++++++++++++------------------------------
 1 file changed, 41 insertions(+), 67 deletions(-)

diff --git a/quickjs-libc.c b/quickjs-libc.c
index e9e276db7..3761e7a18 100644
--- a/quickjs-libc.c
+++ b/quickjs-libc.c
@@ -94,6 +94,8 @@ extern char **environ;
 #include "list.h"
 #include "quickjs-libc.h"
 
+#define MAX_SAFE_INTEGER (((int64_t) 1 << 53) - 1)
+
 /* TODO:
    - add socket calls
 */
@@ -112,7 +114,7 @@ typedef struct {
 
 typedef struct {
     struct list_head link;
-    uint8_t has_object:1;
+    int64_t timer_id;
     uint8_t repeats:1;
     int64_t timeout;
     int64_t delay;
@@ -150,6 +152,7 @@ typedef struct JSThreadState {
     struct list_head os_timers; /* list of JSOSTimer.link */
     struct list_head port_list; /* list of JSWorkerMessageHandler.link */
     int eval_script_recurse; /* only used in the main thread */
+    int64_t next_timer_id; /* for setTimeout / setInterval */
     /* not used in the main thread */
     JSWorkerMessagePipe *recv_pipe, *send_pipe;
 } JSThreadState;
@@ -2029,41 +2032,13 @@ static uint64_t js__hrtime_ms(void)
     return js__hrtime_ns() / (1000 * 1000);
 }
 
-static void unlink_timer(JSRuntime *rt, JSOSTimer *th)
-{
-    if (th->link.prev) {
-        list_del(&th->link);
-        th->link.prev = th->link.next = NULL;
-    }
-}
-
 static void free_timer(JSRuntime *rt, JSOSTimer *th)
 {
+    list_del(&th->link);
     JS_FreeValueRT(rt, th->func);
     js_free_rt(rt, th);
 }
 
-static JSClassID js_os_timer_class_id;
-
-static void js_os_timer_finalizer(JSRuntime *rt, JSValue val)
-{
-    JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id);
-    if (th) {
-        th->has_object = FALSE;
-        if (!th->link.prev)
-            free_timer(rt, th);
-    }
-}
-
-static void js_os_timer_mark(JSRuntime *rt, JSValue val,
-                             JS_MarkFunc *mark_func)
-{
-    JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id);
-    if (th) {
-        JS_MarkValue(rt, th->func, mark_func);
-    }
-}
-
 // TODO(bnoordhuis) accept string as first arg and eval at timer expiry
 // TODO(bnoordhuis) retain argv[2..] as args for callback if argc > 2
 static JSValue js_os_setTimeout(JSContext *ctx, JSValue this_val,
@@ -2074,7 +2049,6 @@ static JSValue js_os_setTimeout(JSContext *ctx, JSValue this_val,
     int64_t delay;
     JSValue func;
     JSOSTimer *th;
-    JSValue obj;
 
     func = argv[0];
     if (!JS_IsFunction(ctx, func))
@@ -2083,42 +2057,50 @@ static JSValue js_os_setTimeout(JSContext *ctx, JSValue this_val,
         return JS_EXCEPTION;
     if (delay < 1)
         delay = 1;
-    obj = JS_NewObjectClass(ctx, js_os_timer_class_id);
-    if (JS_IsException(obj))
-        return obj;
     th = js_mallocz(ctx, sizeof(*th));
-    if (!th) {
-        JS_FreeValue(ctx, obj);
+    if (!th)
         return JS_EXCEPTION;
-    }
-    th->has_object = TRUE;
+    th->timer_id = ts->next_timer_id++;
+    if (ts->next_timer_id > MAX_SAFE_INTEGER)
+        ts->next_timer_id = 1;
     th->repeats = (magic > 0);
     th->timeout = js__hrtime_ms() + delay;
     th->delay = delay;
     th->func = JS_DupValue(ctx, func);
     list_add_tail(&th->link, &ts->os_timers);
-    JS_SetOpaque(obj, th);
-    return obj;
+    return JS_NewInt64(ctx, th->timer_id);
+}
+
+static JSOSTimer *find_timer_by_id(JSThreadState *ts, int timer_id)
+{
+    struct list_head *el;
+    if (timer_id <= 0)
+        return NULL;
+    list_for_each(el, &ts->os_timers) {
+        JSOSTimer *th = list_entry(el, JSOSTimer, link);
+        if (th->timer_id == timer_id)
+            return th;
+    }
+    return NULL;
 }
 
 static JSValue js_os_clearTimeout(JSContext *ctx, JSValue this_val,
                                   int argc, JSValue *argv)
 {
-    JSOSTimer *th = JS_GetOpaque2(ctx, argv[0], js_os_timer_class_id);
-    if (!th)
+    JSRuntime *rt = JS_GetRuntime(ctx);
+    JSThreadState *ts = JS_GetRuntimeOpaque(rt);
+    JSOSTimer *th;
+    int64_t timer_id;
+
+    if (JS_ToInt64(ctx, &timer_id, argv[0]))
         return JS_EXCEPTION;
-    unlink_timer(JS_GetRuntime(ctx), th);
-    JS_FreeValue(ctx, th->func);
-    th->func = JS_UNDEFINED;
+    th = find_timer_by_id(ts, timer_id);
+    if (!th)
+        return JS_UNDEFINED;
+    free_timer(rt, th);
     return JS_UNDEFINED;
 }
 
-static JSClassDef js_os_timer_class = {
-    "OSTimer",
-    .finalizer = js_os_timer_finalizer,
-    .gc_mark = js_os_timer_mark,
-};
-
 /* return a promise */
 static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val,
                                 int argc, JSValueConst *argv)
@@ -2142,7 +2124,7 @@ static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val,
         JS_FreeValue(ctx, resolving_funcs[1]);
         return JS_EXCEPTION;
     }
-    th->has_object = FALSE;
+    th->timer_id = -1;
     th->timeout = js__hrtime_ms() + delay;
     th->func = JS_DupValue(ctx, resolving_funcs[0]);
     list_add_tail(&th->link, &ts->os_timers);
@@ -2185,13 +2167,10 @@ static int js_os_run_timers(JSRuntime *rt, JSContext *ctx, JSThreadState *ts)
             min_delay = min_int(min_delay, delay);
         } else {
             func = JS_DupValueRT(rt, th->func);
-            unlink_timer(rt, th);
-            if (th->repeats) {
+            if (th->repeats)
                 th->timeout = cur_time + th->delay;
-                list_add_tail(&th->link, &ts->os_timers);
-            } else if (!th->has_object) {
+            else
                 free_timer(rt, th);
-            }
             call_handler(ctx, func);
             JS_FreeValueRT(rt, func);
             return 0;
@@ -3785,15 +3764,11 @@ static const JSCFunctionListEntry js_os_funcs[] = {
 
 static int js_os_init(JSContext *ctx, JSModuleDef *m)
 {
-    JSRuntime *rt = JS_GetRuntime(ctx);
     os_poll_func = js_os_poll;
 
-    /* OSTimer class */
-    JS_NewClassID(rt, &js_os_timer_class_id);
-    JS_NewClass(rt, js_os_timer_class_id, &js_os_timer_class);
-
 #ifdef USE_WORKER
     {
+        JSRuntime *rt = JS_GetRuntime(ctx);
         JSThreadState *ts = JS_GetRuntimeOpaque(rt);
         JSValue proto, obj;
         /* Worker class */
@@ -3819,8 +3794,7 @@ static int js_os_init(JSContext *ctx, JSModuleDef *m)
     }
 #endif /* USE_WORKER */
 
-    return JS_SetModuleExportList(ctx, m, js_os_funcs,
-                                  countof(js_os_funcs));
+    return JS_SetModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs));
 }
 
 JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name)
@@ -3904,6 +3878,8 @@ void js_std_init_handlers(JSRuntime *rt)
     init_list_head(&ts->os_timers);
     init_list_head(&ts->port_list);
 
+    ts->next_timer_id = 1;
+
     JS_SetRuntimeOpaque(rt, ts);
 
 #ifdef USE_WORKER
@@ -3936,9 +3912,7 @@ void js_std_free_handlers(JSRuntime *rt)
 
     list_for_each_safe(el, el1, &ts->os_timers) {
         JSOSTimer *th = list_entry(el, JSOSTimer, link);
-        unlink_timer(rt, th);
-        if (!th->has_object)
-            free_timer(rt, th);
+        free_timer(rt, th);
     }
 
 #ifdef USE_WORKER