From 8e166b8f988ce65b272cc069295968595611d884 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 2 Feb 2026 22:46:07 -0600 Subject: [PATCH] gc aware --- source/quickjs.c | 23 +++++-- tests/suite.cm | 154 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 6 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index e5b4441e..e702dc7c 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -1765,7 +1765,7 @@ static int JS_GetOwnPropertyInternal (JSContext *ctx, static __exception int js_get_length32 (JSContext *ctx, uint32_t *pres, JSValue obj); static __exception int js_get_length64 (JSContext *ctx, int64_t *pres, JSValue obj); static void free_arg_list (JSContext *ctx, JSValue *tab, uint32_t len); -static JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue array_arg); +static JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue *parray_arg); static BOOL js_get_fast_array (JSContext *ctx, JSValue obj, JSValue **arrpp, uint32_t *countp); static JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); @@ -18786,13 +18786,13 @@ static void free_arg_list (JSContext *ctx, JSValue *tab, uint32_t len) { } /* XXX: should use ValueArray */ -static JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue array_arg) { +static JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue *parray_arg) { uint32_t len, i; JSValue *tab; /* Fast path for intrinsic arrays */ - if (JS_IsArray (array_arg)) { - JSArray *arr = JS_VALUE_GET_ARRAY (array_arg); + if (JS_IsArray (*parray_arg)) { + JSArray *arr = JS_VALUE_GET_ARRAY (*parray_arg); len = arr->len; if (len > JS_MAX_LOCAL_VARS) { JS_ThrowRangeError ( @@ -18801,6 +18801,7 @@ static JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue array_ar } tab = js_mallocz (ctx, sizeof (tab[0]) * max_uint32 (1, len)); if (!tab) return NULL; + arr = JS_VALUE_GET_ARRAY (*parray_arg); /* re-chase after malloc via argv */ for (i = 0; i < len; i++) { tab[i] = arr->values[i]; } @@ -18832,7 +18833,7 @@ static JSValue js_function_apply (JSContext *ctx, JSValue this_val, int argc, JS if (unlikely (arr->len > f->length)) return JS_ThrowTypeError (ctx, "too many arguments"); } - tab = build_arg_list (ctx, &len, array_arg); + tab = build_arg_list (ctx, &len, &argv[1]); if (!tab) return JS_EXCEPTION; ret = JS_Call (ctx, this_val, this_arg, len, (JSValue *)tab); free_arg_list (ctx, tab, len); @@ -22089,6 +22090,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu /* Copy */ JSValue result = JS_NewArrayLen (ctx, len); if (JS_IsException (result)) return result; + arr = JS_VALUE_GET_ARRAY (argv[0]); /* re-chase after allocation via argv */ JSArray *out = JS_VALUE_GET_ARRAY (result); for (int i = 0; i < len; i++) { out->values[i] = arr->values[i]; @@ -23015,6 +23017,8 @@ static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSVal int len = keys->len; for (int i = 0; i < len; i++) { + keys = JS_VALUE_GET_ARRAY (argv[1]); /* re-chase each iteration */ + if (i >= (int)keys->len) break; JSValue key = keys->values[i]; int key_tag = JS_VALUE_GET_TAG (key); if (key_tag == JS_TAG_STRING || key_tag == JS_TAG_STRING_IMM) { @@ -23043,10 +23047,13 @@ static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSVal JSValue result = JS_NewObject (ctx); if (JS_IsException (result)) return result; + JSGCRef result_ref; int is_func = argc >= 2 && JS_IsFunction (argv[1]); for (int i = 0; i < len; i++) { + keys = JS_VALUE_GET_ARRAY (argv[0]); /* re-chase each iteration via argv */ + if (i >= (int)keys->len) break; JSValue key = keys->values[i]; int key_tag = JS_VALUE_GET_TAG (key); if (key_tag == JS_TAG_STRING || key_tag == JS_TAG_STRING_IMM) { @@ -23057,7 +23064,9 @@ static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSVal val = JS_TRUE; } else if (is_func) { JSValue arg_key = key; + JS_PUSH_VALUE (ctx, result); val = JS_CallInternal (ctx, argv[1], JS_NULL, 1, &arg_key, 0); + JS_POP_VALUE (ctx, result); if (JS_IsException (val)) { return JS_EXCEPTION; } @@ -23103,6 +23112,7 @@ static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSV JSValue *args = js_malloc (ctx, sizeof (JSValue) * len); if (!args) return JS_EXCEPTION; + arr = JS_VALUE_GET_ARRAY (argv[1]); /* re-chase after malloc via argv */ for (int i = 0; i < len; i++) { args[i] = arr->values[i]; @@ -23723,6 +23733,7 @@ static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSVa JSValue result = JS_NewArrayLen (ctx, len); if (JS_IsException (result)) return result; + arr = JS_VALUE_GET_ARRAY (argv[0]); /* re-chase after allocation via argv */ JSArray *out = JS_VALUE_GET_ARRAY (result); for (int i = len - 1, j = 0; i >= 0; i--, j++) { out->values[j] = arr->values[i]; @@ -24354,7 +24365,7 @@ static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue return JS_ThrowTypeError (ctx, "third argument must be an array"); uint32_t len; - JSValue *tab = build_arg_list (ctx, &len, argv[2]); + JSValue *tab = build_arg_list (ctx, &len, &argv[2]); if (!tab) return JS_EXCEPTION; JSValue ret = JS_CallInternal (ctx, func, this_arg, len, tab, 0); diff --git a/tests/suite.cm b/tests/suite.cm index 31d91e38..cb1c2d97 100644 --- a/tests/suite.cm +++ b/tests/suite.cm @@ -3526,4 +3526,158 @@ return { if (obj.beta[1] != obj) throw "text key cycle failed" }, + // ============================================================================ + // OBJECT INTRINSIC TESTS + // ============================================================================ + + test_object_shallow_copy: function() { + var orig = {a: 1, b: 2, c: 3} + var copy = object(orig) + if (copy.a != 1) throw "object copy a failed" + if (copy.b != 2) throw "object copy b failed" + if (copy.c != 3) throw "object copy c failed" + copy.a = 99 + if (orig.a != 1) throw "object copy should not mutate original" + }, + + test_object_combine: function() { + var obj1 = {a: 1, b: 2} + var obj2 = {c: 3, d: 4} + var combined = object(obj1, obj2) + if (combined.a != 1) throw "object combine a failed" + if (combined.b != 2) throw "object combine b failed" + if (combined.c != 3) throw "object combine c failed" + if (combined.d != 4) throw "object combine d failed" + }, + + test_object_combine_override: function() { + var obj1 = {a: 1, b: 2} + var obj2 = {b: 99, c: 3} + var combined = object(obj1, obj2) + if (combined.a != 1) throw "object combine override a failed" + if (combined.b != 99) throw "object combine should override with second arg" + if (combined.c != 3) throw "object combine override c failed" + }, + + test_object_select_keys: function() { + var orig = {a: 1, b: 2, c: 3, d: 4} + var selected = object(orig, ["a", "c"]) + if (selected.a != 1) throw "object select a failed" + if (selected.c != 3) throw "object select c failed" + if (selected.b != null) throw "object select should not include b" + if (selected.d != null) throw "object select should not include d" + }, + + test_object_from_keys_true: function() { + var keys = ["x", "y", "z"] + var obj = object(keys) + if (obj.x != true) throw "object from keys x failed" + if (obj.y != true) throw "object from keys y failed" + if (obj.z != true) throw "object from keys z failed" + }, + + test_object_from_keys_function: function() { + var keys = ["a", "b", "c"] + var obj = object(keys, function(k) { return k + "_val" }) + if (obj.a != "a_val") throw "object from keys func a failed" + if (obj.b != "b_val") throw "object from keys func b failed" + if (obj.c != "c_val") throw "object from keys func c failed" + }, + + // ============================================================================ + // SPLAT INTRINSIC TESTS + // ============================================================================ + + test_splat_prototype_flattening: function() { + var proto = {x: 10, y: 20} + var obj = {z: 30} + obj.__proto__ = proto + var flat = splat(obj) + if (flat.x != 10) throw "splat x failed" + if (flat.y != 20) throw "splat y failed" + if (flat.z != 30) throw "splat z failed" + }, + + // ============================================================================ + // REVERSE INTRINSIC TESTS + // ============================================================================ + + test_reverse_array: function() { + var arr = [1, 2, 3, 4, 5] + var rev = reverse(arr) + if (rev[0] != 5) throw "reverse[0] failed" + if (rev[1] != 4) throw "reverse[1] failed" + if (rev[2] != 3) throw "reverse[2] failed" + if (rev[3] != 2) throw "reverse[3] failed" + if (rev[4] != 1) throw "reverse[4] failed" + if (arr[0] != 1) throw "reverse should not mutate original" + }, + + // ============================================================================ + // APPLY INTRINSIC TESTS + // ============================================================================ + + test_apply_with_array_args: function() { + def sum = function(a, b, c) { return a + b + c } + var result = fn.apply(sum, [1, 2, 3]) + if (result != 6) throw "apply with array args failed" + }, + + test_apply_with_no_args: function() { + def ret42 = function() { return 42 } + var result = fn.apply(ret42) + if (result != 42) throw "apply with no args failed" + }, + + test_apply_with_single_value: function() { + def double = function(x) { return x * 2 } + var result = fn.apply(double, 10) + if (result != 20) throw "apply with single value failed" + }, + + // ============================================================================ + // GC STRESS TESTS FOR FIXED INTRINSICS + // ============================================================================ + + test_gc_reverse_under_pressure: function() { + // Create GC pressure by making many arrays, then reverse + var arrays = [] + for (var i = 0; i < 100; i = i + 1) { + arrays[i] = [i, i+1, i+2, i+3, i+4] + } + // Now reverse each one - this tests re-chase after allocation + for (var i = 0; i < 100; i = i + 1) { + var rev = reverse(arrays[i]) + if (rev[0] != i+4) throw "gc reverse stress failed at " + text(i) + } + }, + + test_gc_object_select_under_pressure: function() { + // Create GC pressure + var objs = [] + for (var i = 0; i < 100; i = i + 1) { + objs[i] = {a: i, b: i+1, c: i+2, d: i+3} + } + // Select keys - tests re-chase in loop + for (var i = 0; i < 100; i = i + 1) { + var selected = object(objs[i], ["a", "c"]) + if (selected.a != i) throw "gc object select stress failed at " + text(i) + if (selected.c != i+2) throw "gc object select stress c failed at " + text(i) + } + }, + + test_gc_object_from_keys_function_under_pressure: function() { + // Create GC pressure + var keysets = [] + for (var i = 0; i < 50; i = i + 1) { + keysets[i] = ["k" + text(i), "j" + text(i), "m" + text(i)] + } + // Create objects with function - tests JS_PUSH/POP and re-chase + for (var i = 0; i < 50; i = i + 1) { + var obj = object(keysets[i], function(k) { return k + "_value" }) + var expected = "k" + text(i) + "_value" + if (obj["k" + text(i)] != expected) throw "gc object from keys func stress failed at " + text(i) + } + }, + }