ocaml style rooting macros

This commit is contained in:
2026-02-13 20:46:31 -06:00
parent e80e615634
commit 83263379bd
24 changed files with 418 additions and 220 deletions

View File

@@ -103,6 +103,7 @@ var v = a[] // pop: v is 3, a is [1, 2]
- Most files don't have headers; files in a package are not shared between packages - Most files don't have headers; files in a package are not shared between packages
- No undefined in C API: use `JS_IsNull` and `JS_NULL` only - No undefined in C API: use `JS_IsNull` and `JS_NULL` only
- A C file with correct macros (`CELL_USE_FUNCS` etc) is loaded as a module by its name (e.g., `png.c` in a package → `use('<package>/png')`) - A C file with correct macros (`CELL_USE_FUNCS` etc) is loaded as a module by its name (e.g., `png.c` in a package → `use('<package>/png')`)
- Use `JS_FRAME`/`JS_ROOT`/`JS_RETURN` macros for any C function that allocates multiple heap objects. Any `JS_New*`/`JS_SetProperty*` call can trigger GC.
## Project Layout ## Project Layout

View File

@@ -381,19 +381,21 @@ static const JSCFunctionListEntry js_reader_funcs[] = {
JSValue js_miniz_use(JSContext *js) JSValue js_miniz_use(JSContext *js)
{ {
JS_FRAME(js);
JS_NewClassID(&js_reader_class_id); JS_NewClassID(&js_reader_class_id);
JS_NewClass(js, js_reader_class_id, &js_reader_class); JS_NewClass(js, js_reader_class_id, &js_reader_class);
JSValue reader_proto = JS_NewObject(js); JS_ROOT(reader_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, reader_proto.val, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_reader_class_id, reader_proto); JS_SetClassProto(js, js_reader_class_id, reader_proto.val);
JS_NewClassID(&js_writer_class_id); JS_NewClassID(&js_writer_class_id);
JS_NewClass(js, js_writer_class_id, &js_writer_class); JS_NewClass(js, js_writer_class_id, &js_writer_class);
JSValue writer_proto = JS_NewObject(js); JS_ROOT(writer_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, writer_proto.val, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_writer_class_id, writer_proto); JS_SetClassProto(js, js_writer_class_id, writer_proto.val);
JSValue export = JS_NewObject(js); JS_ROOT(export, JS_NewObject(js));
JS_SetPropertyFunctionList(js, export, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, export.val, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry));
return export; JS_RETURN(export.val);
} }

View File

@@ -240,7 +240,8 @@ static const JSCFunctionListEntry js_crypto_funcs[] = {
JSValue js_crypto_use(JSContext *js) JSValue js_crypto_use(JSContext *js)
{ {
JSValue obj = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, obj, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0])); JS_ROOT(mod, JS_NewObject(js));
return obj; JS_SetPropertyFunctionList(js, mod.val, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0]));
JS_RETURN(mod.val);
} }

View File

@@ -22,7 +22,8 @@ static const JSCFunctionListEntry js_debug_funcs[] = {
}; };
JSValue js_debug_use(JSContext *js) { JSValue js_debug_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_debug_funcs,countof(js_debug_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_debug_funcs, countof(js_debug_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -21,7 +21,8 @@ static const JSCFunctionListEntry js_js_funcs[] = {
}; };
JSValue js_js_use(JSContext *js) { JSValue js_js_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_js_funcs,countof(js_js_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_js_funcs, countof(js_js_funcs));
JS_RETURN(mod.val);
} }

137
fd.c
View File

@@ -417,36 +417,34 @@ JSC_CCALL(fd_fstat,
if (fstat(fd, &st) != 0) if (fstat(fd, &st) != 0)
return JS_ThrowInternalError(js, "fstat failed: %s", strerror(errno)); return JS_ThrowInternalError(js, "fstat failed: %s", strerror(errno));
JSValue obj = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); JS_ROOT(obj, JS_NewObject(js));
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size));
JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid)); JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode));
JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid)); JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid));
JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime)); JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid));
JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime)); JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime));
JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime)); JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime));
JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink)); JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime));
JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink));
JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino));
JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); JS_SetPropertyStr(js, obj.val, "dev", JS_NewInt32(js, st.st_dev));
JS_SetPropertyStr(js, obj.val, "rdev", JS_NewInt32(js, st.st_rdev));
#ifndef _WIN32 #ifndef _WIN32
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks));
#else #else
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512));
#endif #endif
JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
// Add boolean properties for file type JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode))); JS_RETURN(obj.val);
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
return obj;
) )
JSC_CCALL(fd_stat, JSC_CCALL(fd_stat,
@@ -459,40 +457,39 @@ JSC_CCALL(fd_stat,
return JS_NewObject(js); return JS_NewObject(js);
} }
JSValue obj = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); JS_ROOT(obj, JS_NewObject(js));
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size));
JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid)); JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode));
JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid)); JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid));
JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime)); JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid));
JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime)); JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime));
JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime)); JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime));
JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink)); JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime));
JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink));
JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino));
JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); JS_SetPropertyStr(js, obj.val, "dev", JS_NewInt32(js, st.st_dev));
JS_SetPropertyStr(js, obj.val, "rdev", JS_NewInt32(js, st.st_rdev));
#ifndef _WIN32 #ifndef _WIN32
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks));
#else #else
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512));
#endif #endif
JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
// Add boolean properties for file type JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
JS_FreeCString(js, path); JS_FreeCString(js, path);
return obj; JS_RETURN(obj.val);
) )
JSC_SCALL(fd_readdir, JSC_SCALL(fd_readdir,
JS_FRAME(js);
#ifdef _WIN32 #ifdef _WIN32
WIN32_FIND_DATA ffd; WIN32_FIND_DATA ffd;
char path[PATH_MAX]; char path[PATH_MAX];
@@ -501,28 +498,31 @@ JSC_SCALL(fd_readdir,
if (hFind == INVALID_HANDLE_VALUE) { if (hFind == INVALID_HANDLE_VALUE) {
ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path); ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path);
} else { } else {
ret = JS_NewArray(js); JS_ROOT(arr, JS_NewArray(js));
do { do {
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue; if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
JS_ArrayPush(js, &ret, JS_NewString(js, ffd.cFileName)); JS_ArrayPush(js, &arr.val, JS_NewString(js, ffd.cFileName));
} while (FindNextFile(hFind, &ffd) != 0); } while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind); FindClose(hFind);
ret = arr.val;
} }
#else #else
DIR *d; DIR *d;
struct dirent *dir; struct dirent *dir;
d = opendir(str); d = opendir(str);
if (d) { if (d) {
ret = JS_NewArray(js); JS_ROOT(arr, JS_NewArray(js));
while ((dir = readdir(d)) != NULL) { while ((dir = readdir(d)) != NULL) {
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue;
JS_ArrayPush(js, &ret, JS_NewString(js, dir->d_name)); JS_ArrayPush(js, &arr.val, JS_NewString(js, dir->d_name));
} }
closedir(d); closedir(d);
ret = arr.val;
} else { } else {
ret = JS_ThrowInternalError(js, "opendir failed for %s: %s", str, strerror(errno)); ret = JS_ThrowInternalError(js, "opendir failed for %s: %s", str, strerror(errno));
} }
#endif #endif
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
) )
JSC_CCALL(fd_is_file, JSC_CCALL(fd_is_file,
@@ -585,7 +585,7 @@ JSC_CCALL(fd_slurpwrite,
) )
// Helper function for recursive enumeration // Helper function for recursive enumeration
static void visit_directory(JSContext *js, JSValue results, int *result_count, const char *curr_path, const char *rel_prefix, int recurse) { static void visit_directory(JSContext *js, JSValue *results, int *result_count, const char *curr_path, const char *rel_prefix, int recurse) {
if (!curr_path) return; if (!curr_path) return;
#ifdef _WIN32 #ifdef _WIN32
@@ -602,7 +602,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
} else { } else {
strcpy(item_rel, ffd.cFileName); strcpy(item_rel, ffd.cFileName);
} }
JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel)); JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));
if (recurse) { if (recurse) {
struct stat st; struct stat st;
@@ -627,7 +627,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
} else { } else {
strcpy(item_rel, dir->d_name); strcpy(item_rel, dir->d_name);
} }
JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel)); JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));
if (recurse) { if (recurse) {
struct stat st; struct stat st;
@@ -651,14 +651,16 @@ JSC_SCALL(fd_enumerate,
if (argc > 1) if (argc > 1)
recurse = JS_ToBool(js, argv[1]); recurse = JS_ToBool(js, argv[1]);
JSValue results = JS_NewArray(js); JS_FRAME(js);
JS_ROOT(arr, JS_NewArray(js));
int result_count = 0; int result_count = 0;
struct stat st; struct stat st;
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
visit_directory(js, results, &result_count, path, "", recurse); visit_directory(js, &arr.val, &result_count, path, "", recurse);
ret = results; ret = arr.val;
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
) )
JSC_CCALL(fd_realpath, JSC_CCALL(fd_realpath,
@@ -753,7 +755,8 @@ static const JSCFunctionListEntry js_fd_funcs[] = {
}; };
JSValue js_fd_use(JSContext *js) { JSValue js_fd_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_fd_funcs, countof(js_fd_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_fd_funcs, countof(js_fd_funcs));
JS_RETURN(mod.val);
} }

7
fit.c
View File

@@ -250,7 +250,8 @@ static const JSCFunctionListEntry js_fit_funcs[] = {
JSValue js_fit_use(JSContext *js) JSValue js_fit_use(JSContext *js)
{ {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_fit_funcs, countof(js_fit_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_fit_funcs, countof(js_fit_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -75,7 +75,8 @@ static const JSCFunctionListEntry js_kim_funcs[] = {
JSValue js_kim_use(JSContext *js) JSValue js_kim_use(JSContext *js)
{ {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_kim_funcs, countof(js_kim_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_kim_funcs, countof(js_kim_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -277,29 +277,31 @@ JSC_CCALL(os_mallinfo,
) )
static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) { static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) {
JSValue ret = JS_NULL; JS_FRAME(js);
ret = JS_NewObject(js); JS_ROOT(ret, JS_NewObject(js));
#if defined(__linux__) || defined(__APPLE__) #if defined(__linux__) || defined(__APPLE__)
struct rusage jsmem; struct rusage jsmem;
getrusage(RUSAGE_SELF, &jsmem); getrusage(RUSAGE_SELF, &jsmem);
JSJMEMRET(ru_maxrss); #define JSJMEMRET_GC(FIELD) JS_SetPropertyStr(js, ret.val, #FIELD, number2js(js, jsmem.FIELD));
JSJMEMRET(ru_ixrss); JSJMEMRET_GC(ru_maxrss);
JSJMEMRET(ru_idrss); JSJMEMRET_GC(ru_ixrss);
JSJMEMRET(ru_isrss); JSJMEMRET_GC(ru_idrss);
JSJMEMRET(ru_minflt); JSJMEMRET_GC(ru_isrss);
JSJMEMRET(ru_majflt); JSJMEMRET_GC(ru_minflt);
JSJMEMRET(ru_nswap); JSJMEMRET_GC(ru_majflt);
JSJMEMRET(ru_inblock); JSJMEMRET_GC(ru_nswap);
JSJMEMRET(ru_oublock); JSJMEMRET_GC(ru_inblock);
JSJMEMRET(ru_msgsnd); JSJMEMRET_GC(ru_oublock);
JSJMEMRET(ru_msgrcv); JSJMEMRET_GC(ru_msgsnd);
JSJMEMRET(ru_nsignals); JSJMEMRET_GC(ru_msgrcv);
JSJMEMRET(ru_nvcsw); JSJMEMRET_GC(ru_nsignals);
JSJMEMRET(ru_nivcsw); JSJMEMRET_GC(ru_nvcsw);
JSJMEMRET_GC(ru_nivcsw);
#undef JSJMEMRET_GC
#endif #endif
return ret; JS_RETURN(ret.val);
} }
JSC_SCALL(os_system, JSC_SCALL(os_system,
@@ -628,7 +630,8 @@ JSValue js_os_use(JSContext *js) {
JS_NewClassID(&js_dylib_class_id); JS_NewClassID(&js_dylib_class_id);
JS_NewClass(js, js_dylib_class_id, &js_dylib_class); JS_NewClass(js, js_dylib_class_id, &js_dylib_class);
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_os_funcs, countof(js_os_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -22,6 +22,10 @@ if get_option('validate_gc')
add_project_arguments('-DVALIDATE_GC', language: 'c') add_project_arguments('-DVALIDATE_GC', language: 'c')
endif endif
if get_option('force_gc')
add_project_arguments('-DFORCE_GC_AT_MALLOC', language: 'c')
endif
deps = [] deps = []
if host_machine.system() == 'darwin' if host_machine.system() == 'darwin'

View File

@@ -1,2 +1,4 @@
option('validate_gc', type: 'boolean', value: false, option('validate_gc', type: 'boolean', value: false,
description: 'Enable GC validation checks (stale pointer detection, pre-GC frame validation)') description: 'Enable GC validation checks (stale pointer detection, pre-GC frame validation)')
option('force_gc', type: 'boolean', value: false,
description: 'Force GC on every allocation (makes stale pointer bugs deterministic)')

View File

@@ -570,19 +570,21 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
JSValue js_enet_use(JSContext *ctx) JSValue js_enet_use(JSContext *ctx)
{ {
JS_FRAME(ctx);
JS_NewClassID(&enet_host_id); JS_NewClassID(&enet_host_id);
JS_NewClass(ctx, enet_host_id, &enet_host); JS_NewClass(ctx, enet_host_id, &enet_host);
JSValue host_proto = JS_NewObject(ctx); JS_ROOT(host_proto, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs)); JS_SetPropertyFunctionList(ctx, host_proto.val, js_enet_host_funcs, countof(js_enet_host_funcs));
JS_SetClassProto(ctx, enet_host_id, host_proto); JS_SetClassProto(ctx, enet_host_id, host_proto.val);
JS_NewClassID(&enet_peer_class_id); JS_NewClassID(&enet_peer_class_id);
JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class); JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class);
JSValue peer_proto = JS_NewObject(ctx); JS_ROOT(peer_proto, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs)); JS_SetPropertyFunctionList(ctx, peer_proto.val, js_enet_peer_funcs, countof(js_enet_peer_funcs));
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto); JS_SetClassProto(ctx, enet_peer_class_id, peer_proto.val);
JSValue export_obj = JS_NewObject(ctx); JS_ROOT(export_obj, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, export_obj, js_enet_funcs, countof(js_enet_funcs)); JS_SetPropertyFunctionList(ctx, export_obj.val, js_enet_funcs, countof(js_enet_funcs));
return export_obj; JS_RETURN(export_obj.val);
} }

View File

@@ -319,9 +319,10 @@ static const JSCFunctionListEntry js_http_funcs[] = {
}; };
JSValue js_http_use(JSContext *js) { JSValue js_http_use(JSContext *js) {
JS_FRAME(js);
par_easycurl_init(0); // Initialize platform HTTP backend par_easycurl_init(0); // Initialize platform HTTP backend
JSValue obj = JS_NewObject(js); JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, obj, js_http_funcs, JS_SetPropertyFunctionList(js, mod.val, js_http_funcs,
sizeof(js_http_funcs)/sizeof(js_http_funcs[0])); sizeof(js_http_funcs)/sizeof(js_http_funcs[0]));
return obj; JS_RETURN(mod.val);
} }

View File

@@ -595,26 +595,27 @@ static const JSCFunctionListEntry js_socket_funcs[] = {
}; };
JSValue js_socket_use(JSContext *js) { JSValue js_socket_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_socket_funcs, countof(js_socket_funcs)); JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_socket_funcs, countof(js_socket_funcs));
// Add constants // Add constants
JS_SetPropertyStr(js, mod, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC)); JS_SetPropertyStr(js, mod.val, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC));
JS_SetPropertyStr(js, mod, "AF_INET", JS_NewInt32(js, AF_INET)); JS_SetPropertyStr(js, mod.val, "AF_INET", JS_NewInt32(js, AF_INET));
JS_SetPropertyStr(js, mod, "AF_INET6", JS_NewInt32(js, AF_INET6)); JS_SetPropertyStr(js, mod.val, "AF_INET6", JS_NewInt32(js, AF_INET6));
JS_SetPropertyStr(js, mod, "AF_UNIX", JS_NewInt32(js, AF_UNIX)); JS_SetPropertyStr(js, mod.val, "AF_UNIX", JS_NewInt32(js, AF_UNIX));
JS_SetPropertyStr(js, mod, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM)); JS_SetPropertyStr(js, mod.val, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM));
JS_SetPropertyStr(js, mod, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM)); JS_SetPropertyStr(js, mod.val, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM));
JS_SetPropertyStr(js, mod, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE)); JS_SetPropertyStr(js, mod.val, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE));
JS_SetPropertyStr(js, mod, "SHUT_RD", JS_NewInt32(js, SHUT_RD)); JS_SetPropertyStr(js, mod.val, "SHUT_RD", JS_NewInt32(js, SHUT_RD));
JS_SetPropertyStr(js, mod, "SHUT_WR", JS_NewInt32(js, SHUT_WR)); JS_SetPropertyStr(js, mod.val, "SHUT_WR", JS_NewInt32(js, SHUT_WR));
JS_SetPropertyStr(js, mod, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR)); JS_SetPropertyStr(js, mod.val, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR));
JS_SetPropertyStr(js, mod, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET)); JS_SetPropertyStr(js, mod.val, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET));
JS_SetPropertyStr(js, mod, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR)); JS_SetPropertyStr(js, mod.val, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR));
return mod; JS_RETURN(mod.val);
} }

20
qop.c
View File

@@ -457,19 +457,21 @@ static const JSCFunctionListEntry js_qop_funcs[] = {
}; };
JSValue js_qop_use(JSContext *js) { JSValue js_qop_use(JSContext *js) {
JS_FRAME(js);
JS_NewClassID(&js_qop_archive_class_id); JS_NewClassID(&js_qop_archive_class_id);
JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class); JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class);
JSValue archive_proto = JS_NewObject(js); JS_ROOT(archive_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs)); JS_SetPropertyFunctionList(js, archive_proto.val, js_qop_archive_funcs, countof(js_qop_archive_funcs));
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto); JS_SetClassProto(js, js_qop_archive_class_id, archive_proto.val);
JS_NewClassID(&js_qop_writer_class_id); JS_NewClassID(&js_qop_writer_class_id);
JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class); JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class);
JSValue writer_proto = JS_NewObject(js); JS_ROOT(writer_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs)); JS_SetPropertyFunctionList(js, writer_proto.val, js_qop_writer_funcs, countof(js_qop_writer_funcs));
JS_SetClassProto(js, js_qop_writer_class_id, writer_proto); JS_SetClassProto(js, js_qop_writer_class_id, writer_proto.val);
JSValue mod = JS_NewObject(js); JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod, js_qop_funcs, countof(js_qop_funcs)); JS_SetPropertyFunctionList(js, mod.val, js_qop_funcs, countof(js_qop_funcs));
return mod; JS_RETURN(mod.val);
} }

View File

@@ -275,34 +275,47 @@ void script_startup(cell_rt *prt)
} }
// Create hidden environment // Create hidden environment
JSValue hidden_env = JS_NewObject(js); // Note: evaluate allocating calls into temporaries before passing to
JS_SetPropertyStr(js, hidden_env, "os", js_os_use(js)); // JS_SetPropertyStr, so env_ref.val is read AFTER GC may have moved it.
JS_SetPropertyStr(js, hidden_env, "json", js_json_use(js)); JSGCRef env_ref;
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js)); JS_AddGCRef(js, &env_ref);
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js)); env_ref.val = JS_NewObject(js);
JSValue tmp;
tmp = js_os_use(js);
JS_SetPropertyStr(js, env_ref.val, "os", tmp);
tmp = js_json_use(js);
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
tmp = js_nota_use(js);
JS_SetPropertyStr(js, env_ref.val, "nota", tmp);
tmp = js_wota_use(js);
JS_SetPropertyStr(js, env_ref.val, "wota", tmp);
crt->actor_sym_ref.val = JS_NewObject(js); crt->actor_sym_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val)); JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
// Always set init (even if null) // Always set init (even if null)
if (crt->init_wota) { if (crt->init_wota) {
JS_SetPropertyStr(js, hidden_env, "init", wota2value(js, crt->init_wota)); tmp = wota2value(js, crt->init_wota);
JS_SetPropertyStr(js, env_ref.val, "init", tmp);
free(crt->init_wota); free(crt->init_wota);
crt->init_wota = NULL; crt->init_wota = NULL;
} else { } else {
JS_SetPropertyStr(js, hidden_env, "init", JS_NULL); JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
} }
// Set args to null for actor spawn (not CLI mode) // Set args to null for actor spawn (not CLI mode)
JS_SetPropertyStr(js, hidden_env, "args", JS_NULL); JS_SetPropertyStr(js, env_ref.val, "args", JS_NULL);
if (core_path) if (core_path) {
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path)); tmp = JS_NewString(js, core_path);
JS_SetPropertyStr(js, hidden_env, "shop_path", JS_SetPropertyStr(js, env_ref.val, "core_path", tmp);
shop_path ? JS_NewString(js, shop_path) : JS_NULL); }
tmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
JS_SetPropertyStr(js, env_ref.val, "shop_path", tmp);
// Stone the environment // Stone the environment
hidden_env = JS_Stone(js, hidden_env); JSValue hidden_env = JS_Stone(js, env_ref.val);
JS_DeleteGCRef(js, &env_ref);
// Run from binary // Run from binary
crt->state = ACTOR_RUNNING; crt->state = ACTOR_RUNNING;
@@ -504,23 +517,35 @@ int cell_init(int argc, char **argv)
JS_FreeValue(ctx, js_blob_use(ctx)); JS_FreeValue(ctx, js_blob_use(ctx));
JSValue hidden_env = JS_NewObject(ctx); JSGCRef env_ref;
JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx)); JS_AddGCRef(ctx, &env_ref);
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path)); env_ref.val = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, hidden_env, "shop_path", JSValue tmp;
shop_path ? JS_NewString(ctx, shop_path) : JS_NULL); tmp = js_os_use(ctx);
JS_SetPropertyStr(ctx, hidden_env, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val)); JS_SetPropertyStr(ctx, env_ref.val, "os", tmp);
JS_SetPropertyStr(ctx, hidden_env, "json", js_json_use(ctx)); tmp = JS_NewString(ctx, core_path);
JS_SetPropertyStr(ctx, hidden_env, "nota", js_nota_use(ctx)); JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
JS_SetPropertyStr(ctx, hidden_env, "wota", js_wota_use(ctx)); tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
JS_SetPropertyStr(ctx, hidden_env, "init", JS_NULL); JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp);
JSValue args_arr = JS_NewArray(ctx); JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
tmp = js_json_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
tmp = js_nota_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "nota", tmp);
tmp = js_wota_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "wota", tmp);
JS_SetPropertyStr(ctx, env_ref.val, "init", JS_NULL);
JSGCRef args_ref;
JS_AddGCRef(ctx, &args_ref);
args_ref.val = JS_NewArray(ctx);
for (int i = arg_start; i < argc; i++) { for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]); JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_arr, str); JS_ArrayPush(ctx, &args_ref.val, str);
} }
JS_SetPropertyStr(ctx, hidden_env, "args", args_arr); JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
hidden_env = JS_Stone(ctx, hidden_env); JS_DeleteGCRef(ctx, &args_ref);
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
JS_DeleteGCRef(ctx, &env_ref);
JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env); JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
free(bin_data); free(bin_data);

View File

@@ -156,6 +156,43 @@ JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \
#define countof(x) (sizeof(x)/sizeof((x)[0])) #define countof(x) (sizeof(x)/sizeof((x)[0]))
/* GC safety macros for C functions that allocate multiple heap objects.
Any allocation call (JS_NewObject, JS_SetPropertyStr, etc.) can trigger GC.
JS_ROOT style: explicit, use .val to access the rooted value.
JS_LOCAL style: transparent, GC updates the C local through a pointer. */
#define JS_FRAME(ctx) \
JSContext *_js_ctx = (ctx); \
JSGCRef *_js_gc_frame = JS_GetGCFrame(_js_ctx); \
JSLocalRef *_js_local_frame = JS_GetLocalFrame(_js_ctx)
#define JS_ROOT(name, init) \
JSGCRef name; \
JS_PushGCRef(_js_ctx, &name); \
name.val = (init)
#define JS_LOCAL(name, init) \
JSValue name = (init); \
JSLocalRef name##__lr; \
name##__lr.ptr = &name; \
JS_PushLocalRef(_js_ctx, &name##__lr)
#define JS_RETURN(val) do { \
JSValue _js_ret = (val); \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return _js_ret; \
} while (0)
#define JS_RETURN_NULL() do { \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return JS_NULL; \
} while (0)
#define JS_RETURN_EX() do { \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return JS_EXCEPTION; \
} while (0)
// Common macros for property access // Common macros for property access
#define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\ #define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \ JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \

View File

@@ -1127,6 +1127,17 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
result = frame->slots[a]; result = frame->slots[a];
if (JS_IsNull(frame->caller)) goto done; if (JS_IsNull(frame->caller)) goto done;
{ {
#ifdef VALIDATE_GC
const char *callee_name = "?";
const char *callee_file = "?";
{
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(frame->function);
if (callee_fn->kind == JS_FUNC_KIND_REGISTER && callee_fn->u.reg.code) {
if (callee_fn->u.reg.code->name_cstr) callee_name = callee_fn->u.reg.code->name_cstr;
if (callee_fn->u.reg.code->filename_cstr) callee_file = callee_fn->u.reg.code->filename_cstr;
}
}
#endif
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
frame->caller = JS_NULL; frame->caller = JS_NULL;
frame = caller; frame = caller;
@@ -1143,8 +1154,11 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
void *rp = JS_VALUE_GET_PTR(result); void *rp = JS_VALUE_GET_PTR(result);
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) { if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
if (!is_ct_ptr(ctx, rp)) if (!is_ct_ptr(ctx, rp))
fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u\n", fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u callee=%s (%s) caller=%s (%s)\n",
ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc); ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc,
callee_name, callee_file,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?");
} }
} }
#endif #endif
@@ -1617,9 +1631,15 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
if (JS_IsPtr(ret)) { if (JS_IsPtr(ret)) {
void *rp = JS_VALUE_GET_PTR(ret); void *rp = JS_VALUE_GET_PTR(ret);
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) { if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
if (!is_ct_ptr(ctx, rp)) if (!is_ct_ptr(ctx, rp)) {
fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d\n", int magic = (fn->kind == JS_FUNC_KIND_C) ? fn->u.cfunc.magic : -1;
b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind); void *cfp = (fn->kind == JS_FUNC_KIND_C) ? (void *)fn->u.cfunc.c_function.generic : NULL;
fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d magic=%d cfunc=%p caller=%s (%s)\n",
b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind,
magic, cfp,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?");
}
} }
} }
#endif #endif

View File

@@ -156,7 +156,8 @@ static const JSCFunctionListEntry js_actor_funcs[] = {
}; };
JSValue js_actor_use(JSContext *js) { JSValue js_actor_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_actor_funcs,countof(js_actor_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_actor_funcs, countof(js_actor_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -1092,6 +1092,7 @@ struct JSContext {
JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */ JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */ JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
JSLocalRef *top_local_ref; /* for JS_LOCAL macro - GC updates C locals through pointers */
CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */ CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */
int class_count; /* size of class_array and class_proto */ int class_count; /* size of class_array and class_proto */
@@ -1185,7 +1186,15 @@ static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size);
/* Helper to check if a pointer is in constant text pool memory */ /* Helper to check if a pointer is in constant text pool memory */
static inline int is_ct_ptr (JSContext *ctx, void *ptr) { static inline int is_ct_ptr (JSContext *ctx, void *ptr) {
return (uint8_t *)ptr >= ctx->ct_base && (uint8_t *)ptr < ctx->ct_end; uint8_t *p = (uint8_t *)ptr;
if (p >= ctx->ct_base && p < ctx->ct_end) return 1;
/* Also check overflow pages */
CTPage *page = (CTPage *)ctx->ct_pages;
while (page) {
if (p >= page->data && p < page->data + page->size) return 1;
page = page->next;
}
return 0;
} }
#ifdef HEAP_CHECK #ifdef HEAP_CHECK

View File

@@ -146,10 +146,22 @@ typedef struct JSGCRef {
struct JSGCRef *prev; struct JSGCRef *prev;
} JSGCRef; } JSGCRef;
/* JSLocalRef - GC updates C locals through pointers (OCaml-style) */
typedef struct JSLocalRef {
JSValue *ptr;
struct JSLocalRef *prev;
} JSLocalRef;
/* stack of JSGCRef */ /* stack of JSGCRef */
JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref); JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref);
JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref); JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref);
/* JS_FRAME/JS_ROOT/JS_LOCAL helpers (for use from cell.h macros) */
JSGCRef *JS_GetGCFrame(JSContext *ctx);
JSLocalRef *JS_GetLocalFrame(JSContext *ctx);
void JS_PushLocalRef(JSContext *ctx, JSLocalRef *ref);
void JS_RestoreFrame(JSContext *ctx, JSGCRef *gc_frame, JSLocalRef *local_frame);
#define JS_PUSH_VALUE(ctx, v) do { JS_PushGCRef(ctx, &v ## _ref); v ## _ref.val = v; } while (0) #define JS_PUSH_VALUE(ctx, v) do { JS_PushGCRef(ctx, &v ## _ref); v ## _ref.val = v; } while (0)
#define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref) #define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref)

View File

@@ -183,6 +183,25 @@ void JS_DeleteGCRef (JSContext *ctx, JSGCRef *ref) {
} }
} }
/* JS_FRAME/JS_ROOT/JS_LOCAL helper functions */
JSGCRef *JS_GetGCFrame (JSContext *ctx) {
return ctx->top_gc_ref;
}
JSLocalRef *JS_GetLocalFrame (JSContext *ctx) {
return ctx->top_local_ref;
}
void JS_PushLocalRef (JSContext *ctx, JSLocalRef *ref) {
ref->prev = ctx->top_local_ref;
ctx->top_local_ref = ref;
}
void JS_RestoreFrame (JSContext *ctx, JSGCRef *gc_frame, JSLocalRef *local_frame) {
ctx->top_gc_ref = gc_frame;
ctx->top_local_ref = local_frame;
}
void *ct_alloc (JSContext *ctx, size_t bytes, size_t align) { void *ct_alloc (JSContext *ctx, size_t bytes, size_t align) {
/* Align the request */ /* Align the request */
bytes = (bytes + align - 1) & ~(align - 1); bytes = (bytes + align - 1) & ~(align - 1);
@@ -1608,6 +1627,14 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end);
} }
/* Copy JS_LOCAL roots (update C locals through pointers) */
#ifdef DUMP_GC_DETAIL
printf(" roots: top_local_ref\n"); fflush(stdout);
#endif
for (JSLocalRef *ref = ctx->top_local_ref; ref != NULL; ref = ref->prev) {
*ref->ptr = gc_copy_value (ctx, *ref->ptr, from_base, from_end, to_base, &to_free, to_end);
}
/* Copy JS_AddGCRef/JS_DeleteGCRef roots */ /* Copy JS_AddGCRef/JS_DeleteGCRef roots */
#ifdef DUMP_GC_DETAIL #ifdef DUMP_GC_DETAIL
printf(" roots: last_gc_ref\n"); fflush(stdout); printf(" roots: last_gc_ref\n"); fflush(stdout);
@@ -4383,6 +4410,10 @@ JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj,
ctx->trace_hook (ctx, JS_HOOK_CALL, &dbg, ctx->trace_data); ctx->trace_hook (ctx, JS_HOOK_CALL, &dbg, ctx->trace_data);
} }
#ifdef VALIDATE_GC
uint8_t *pre_heap_base = ctx->heap_base;
#endif
switch (cproto) { switch (cproto) {
case JS_CFUNC_generic: case JS_CFUNC_generic:
ret_val = func.generic (ctx, this_obj, argc, arg_copy); ret_val = func.generic (ctx, this_obj, argc, arg_copy);
@@ -4449,6 +4480,21 @@ JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj,
abort (); abort ();
} }
#ifdef VALIDATE_GC
if (ctx->heap_base != pre_heap_base && JS_IsPtr (ret_val)) {
void *rp = JS_VALUE_GET_PTR (ret_val);
if (!is_ct_ptr (ctx, rp) &&
((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free)) {
/* Note: f is stale after GC (func_obj was passed by value), so we
cannot read f->name here. Just report the pointer. */
fprintf (stderr, "VALIDATE_GC: C function returned stale ptr=%p "
"heap=[%p,%p) after GC\n", rp,
(void *)ctx->heap_base, (void *)ctx->heap_free);
fflush (stderr);
}
}
#endif
ctx->c_call_root = root.prev; ctx->c_call_root = root.prev;
if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET)) if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET))
@@ -5356,19 +5402,27 @@ static JSValue cjson_to_jsvalue (JSContext *ctx, const cJSON *item) {
if (cJSON_IsString (item)) return JS_NewString (ctx, item->valuestring); if (cJSON_IsString (item)) return JS_NewString (ctx, item->valuestring);
if (cJSON_IsArray (item)) { if (cJSON_IsArray (item)) {
int n = cJSON_GetArraySize (item); int n = cJSON_GetArraySize (item);
JSValue arr = JS_NewArrayLen (ctx,n); JSGCRef arr_ref;
JS_AddGCRef (ctx, &arr_ref);
arr_ref.val = JS_NewArrayLen (ctx, n);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
cJSON *child = cJSON_GetArrayItem (item, i); cJSON *child = cJSON_GetArrayItem (item, i);
JS_SetPropertyNumber (ctx, arr, i, cjson_to_jsvalue (ctx, child)); JS_SetPropertyNumber (ctx, arr_ref.val, i, cjson_to_jsvalue (ctx, child));
} }
return arr; JSValue result = arr_ref.val;
JS_DeleteGCRef (ctx, &arr_ref);
return result;
} }
if (cJSON_IsObject (item)) { if (cJSON_IsObject (item)) {
JSValue obj = JS_NewObject (ctx); JSGCRef obj_ref;
JS_AddGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
for (cJSON *child = item->child; child; child = child->next) { for (cJSON *child = item->child; child; child = child->next) {
JS_SetPropertyStr (ctx, obj, child->string, cjson_to_jsvalue (ctx, child)); JS_SetPropertyStr (ctx, obj_ref.val, child->string, cjson_to_jsvalue (ctx, child));
} }
return obj; JSValue result = obj_ref.val;
JS_DeleteGCRef (ctx, &obj_ref);
return result;
} }
return JS_NULL; return JS_NULL;
} }
@@ -11879,9 +11933,13 @@ static const JSCFunctionListEntry js_math_radians_funcs[]
JS_CFUNC_DEF ("e", 1, js_math_e) }; JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_math_radians_use (JSContext *ctx) { JSValue js_math_radians_use (JSContext *ctx) {
JSValue obj = JS_NewObject (ctx); JSGCRef obj_ref;
JS_SetPropertyFunctionList (ctx, obj, js_math_radians_funcs, countof (js_math_radians_funcs)); JS_PushGCRef (ctx, &obj_ref);
return obj; obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_radians_funcs, countof (js_math_radians_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
} }
/* ============================================================================ /* ============================================================================
@@ -11945,9 +12003,13 @@ static const JSCFunctionListEntry js_math_degrees_funcs[]
JS_CFUNC_DEF ("e", 1, js_math_e) }; JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_math_degrees_use (JSContext *ctx) { JSValue js_math_degrees_use (JSContext *ctx) {
JSValue obj = JS_NewObject (ctx); JSGCRef obj_ref;
JS_SetPropertyFunctionList (ctx, obj, js_math_degrees_funcs, countof (js_math_degrees_funcs)); JS_PushGCRef (ctx, &obj_ref);
return obj; obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_degrees_funcs, countof (js_math_degrees_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
} }
/* ============================================================================ /* ============================================================================
@@ -12010,9 +12072,13 @@ static const JSCFunctionListEntry js_math_cycles_funcs[]
JS_CFUNC_DEF ("e", 1, js_math_e) }; JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_math_cycles_use (JSContext *ctx) { JSValue js_math_cycles_use (JSContext *ctx) {
JSValue obj = JS_NewObject (ctx); JSGCRef obj_ref;
JS_SetPropertyFunctionList (ctx, obj, js_math_cycles_funcs, countof (js_math_cycles_funcs)); JS_PushGCRef (ctx, &obj_ref);
return obj; obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_cycles_funcs, countof (js_math_cycles_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
} }
/* Public API: get stack trace as cJSON array */ /* Public API: get stack trace as cJSON array */
cJSON *JS_GetStack(JSContext *ctx) { cJSON *JS_GetStack(JSContext *ctx) {

7
time.c
View File

@@ -71,12 +71,13 @@ static const JSCFunctionListEntry js_time_funcs[] = {
JSValue JSValue
js_internal_time_use(JSContext *ctx) js_internal_time_use(JSContext *ctx)
{ {
JSValue obj = JS_NewObject(ctx); JS_FRAME(ctx);
JS_SetPropertyFunctionList(ctx, obj, JS_ROOT(mod, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, mod.val,
js_time_funcs, js_time_funcs,
sizeof(js_time_funcs) / sizeof(js_time_funcs) /
sizeof(js_time_funcs[0])); sizeof(js_time_funcs[0]));
return obj; JS_RETURN(mod.val);
} }
JSValue JSValue

View File

@@ -29,7 +29,8 @@ static const JSCFunctionListEntry js_wildstar_funcs[] = {
}; };
JSValue js_wildstar_use(JSContext *js) { JSValue js_wildstar_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_wildstar_funcs, countof(js_wildstar_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_wildstar_funcs, countof(js_wildstar_funcs));
JS_RETURN(mod.val);
} }