From 42e29f6816daa82a225263e1ac412b26f1523aee Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Thu, 21 Nov 2024 18:41:32 +0100 Subject: [PATCH] Simplify Array.prototype.sort by sorting in place using quicksort. Don't use libc qsort (which uses malloc on gnu libc) which can leak memory if the callback throws an exception. --- jsarray.c | 222 ++++++++++++++++++++++++++++++++++++------------------ jsgc.c | 6 -- jsi.h | 1 - 3 files changed, 149 insertions(+), 80 deletions(-) diff --git a/jsarray.c b/jsarray.c index 4dea511..9213a84 100644 --- a/jsarray.c +++ b/jsarray.c @@ -233,49 +233,154 @@ static void Ap_slice(js_State *J) js_setindex(J, -2, n); } -struct sortslot { - js_Value v; - js_State *J; -}; - -static int sortcmp(const void *avoid, const void *bvoid) +static int Ap_sort_cmp(js_State *J, int idx_a, int idx_b) { - const struct sortslot *aslot = avoid, *bslot = bvoid; - const js_Value *a = &aslot->v, *b = &bslot->v; - js_State *J = aslot->J; - const char *sx, *sy; - double v; - int c; - - int unx = (a->t.type == JS_TUNDEFINED); - int uny = (b->t.type == JS_TUNDEFINED); - if (unx) return !uny; - if (uny) return -1; - - if (js_iscallable(J, 1)) { - js_copy(J, 1); /* copy function */ - js_pushundefined(J); - js_pushvalue(J, *a); - js_pushvalue(J, *b); - js_call(J, 2); - v = js_tonumber(J, -1); - c = (v == 0) ? 0 : (v < 0) ? -1 : 1; - js_pop(J, 1); + js_Object *obj = js_tovalue(J, 0)->u.object; + if (obj->u.a.simple) { + js_Value *val_a = &obj->u.a.array[idx_a]; + js_Value *val_b = &obj->u.a.array[idx_b]; + int und_a = val_a->t.type == JS_TUNDEFINED; + int und_b = val_b->t.type == JS_TUNDEFINED; + if (und_a) return und_b; + if (und_b) return -1; + if (js_iscallable(J, 1)) { + double v; + js_copy(J, 1); /* copy function */ + js_pushundefined(J); /* no 'this' binding */ + js_pushvalue(J, *val_a); + js_pushvalue(J, *val_b); + js_call(J, 2); + v = js_tonumber(J, -1); + js_pop(J, 1); + if (isnan(v)) + return 0; + if (v == 0) + return 0; + return v < 0 ? -1 : 1; + } else { + const char *str_a, *str_b; + int c; + js_pushvalue(J, *val_a); + js_pushvalue(J, *val_b); + str_a = js_tostring(J, -2); + str_b = js_tostring(J, -1); + c = strcmp(str_a, str_b); + js_pop(J, 2); + return c; + } } else { - js_pushvalue(J, *a); - js_pushvalue(J, *b); - sx = js_tostring(J, -2); - sy = js_tostring(J, -1); - c = strcmp(sx, sy); - js_pop(J, 2); + int und_a, und_b; + int has_a = js_hasindex(J, 0, idx_a); + int has_b = js_hasindex(J, 0, idx_b); + if (!has_a && !has_b) { + return 0; + } + if (has_a && !has_b) { + js_pop(J, 1); + return -1; + } + if (!has_a && has_b) { + js_pop(J, 1); + return 1; + } + + und_a = js_isundefined(J, -2); + und_b = js_isundefined(J, -1); + if (und_a) { + js_pop(J, 2); + return und_b; + } + if (und_b) { + js_pop(J, 2); + return -1; + } + + if (js_iscallable(J, 1)) { + double v; + js_copy(J, 1); /* copy function */ + js_pushundefined(J); /* no 'this' binding */ + js_copy(J, -4); + js_copy(J, -4); + js_call(J, 2); + v = js_tonumber(J, -1); + js_pop(J, 3); + if (isnan(v)) + return 0; + if (v == 0) + return 0; + return v < 0 ? -1 : 1; + } else { + const char *str_a = js_tostring(J, -2); + const char *str_b = js_tostring(J, -1); + int c = strcmp(str_a, str_b); + js_pop(J, 2); + return c; + } } - return c; +} + +static void Ap_sort_swap(js_State *J, int idx_a, int idx_b) +{ + js_Object *obj = js_tovalue(J, 0)->u.object; + if (obj->u.a.simple) { + js_Value tmp = obj->u.a.array[idx_a]; + obj->u.a.array[idx_a] = obj->u.a.array[idx_b]; + obj->u.a.array[idx_b] = tmp; + } else { + int has_a = js_hasindex(J, 0, idx_a); + int has_b = js_hasindex(J, 0, idx_b); + if (has_a && has_b) { + js_setindex(J, 0, idx_a); + js_setindex(J, 0, idx_b); + } else if (has_a && !has_b) { + js_delindex(J, 0, idx_a); + js_setindex(J, 0, idx_b); + } else if (!has_a && has_b) { + js_delindex(J, 0, idx_b); + js_setindex(J, 0, idx_a); + } + } +} + +static int Ap_sort_quicksort_partition(js_State *J, int a, int b) +{ + int pivot = (a + b) >> 1; + while (a <= b) { + while (Ap_sort_cmp(J, a, pivot) < 0) + ++a; + while (Ap_sort_cmp(J, b, pivot) > 0) + --b; + if (a <= b) { + Ap_sort_swap(J, a, b); + ++a; + --b; + } + } + return a; +} + +static void Ap_sort_quicksort(js_State *J, int a, int b) +{ + int i, j, m; + + /* insertion sort small fragments */ + if (b - a < 10) { + for (i = a+1; i < b; ++i) + for (j = i; j > a && Ap_sort_cmp(J, j-1, j) > 0; --j) + Ap_sort_swap(J, j-1, j); + return; + } + + m = Ap_sort_quicksort_partition(J, a, b); + if (a < m - 1) + Ap_sort_quicksort(J, a, m - 1); + if (m < b) + Ap_sort_quicksort(J, m, b); } static void Ap_sort(js_State *J) { - struct sortslot * volatile array = NULL; - int i, n, len; + int len; len = js_getlength(J, 0); if (len <= 0) { @@ -283,47 +388,18 @@ static void Ap_sort(js_State *J) return; } - if (len >= INT_MAX / (int)sizeof(*array)) + if (!js_iscallable(J, 1) && !js_isundefined(J, 1)) + js_typeerror(J, "comparison function must be a function or undefined"); + + if (len >= INT_MAX) js_rangeerror(J, "array is too large to sort"); - /* Holding objects where the GC cannot see them is illegal, but if we - * don't allow the GC to run we can use qsort() on a temporary array of - * js_Values for fast sorting. - */ - ++J->gcpause; - - if (js_try(J)) { - --J->gcpause; - js_free(J, array); - js_throw(J); + if (len <= 1) { + js_copy(J, 0); + return; } - array = js_malloc(J, len * sizeof *array); - - n = 0; - for (i = 0; i < len; ++i) { - if (js_hasindex(J, 0, i)) { - array[n].v = *js_tovalue(J, -1); - array[n].J = J; - js_pop(J, 1); - ++n; - } - } - - qsort(array, n, sizeof *array, sortcmp); - - for (i = 0; i < n; ++i) { - js_pushvalue(J, array[i].v); - js_setindex(J, 0, i); - } - for (i = len-i; i >= n; --i) { - js_delindex(J, 0, i); - } - - --J->gcpause; - - js_endtry(J); - js_free(J, array); + Ap_sort_quicksort(J, 0, len - 1); js_copy(J, 0); } diff --git a/jsgc.c b/jsgc.c index d12d3da..cd0f74d 100644 --- a/jsgc.c +++ b/jsgc.c @@ -147,12 +147,6 @@ void js_gc(js_State *J, int report) int mark; int i; - if (J->gcpause) { - if (report) - js_report(J, "garbage collector is paused"); - return; - } - mark = J->gcmark = J->gcmark == 1 ? 2 : 1; /* Add initial roots. */ diff --git a/jsi.h b/jsi.h index 345ed46..6956d97 100644 --- a/jsi.h +++ b/jsi.h @@ -261,7 +261,6 @@ struct js_State js_Value *stack; /* garbage collector list */ - int gcpause; int gcmark; unsigned int gccounter, gcthresh; js_Environment *gcenv;