MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add proper free list and block memory realloc

+175 -70
+2 -1
include/ant.h
··· 15 15 struct js *js_create(void *buf, size_t len); 16 16 struct js *js_create_dynamic(size_t initial_size, size_t max_size); 17 17 18 - void js_gc(struct js *); 18 + uint32_t js_gc(struct js *); 19 19 void js_destroy(struct js *); 20 + void js_protect_init_memory(struct js *); 20 21 21 22 jsval_t js_glob(struct js *); 22 23 jsval_t js_eval(struct js *, const char *, size_t);
+1 -1
meson.build
··· 68 68 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 69 69 70 70 version_conf = configuration_data() 71 - version_conf.set('ANT_VERSION', '0.0.7.3') 71 + version_conf.set('ANT_VERSION', '0.0.7.4') 72 72 version_conf.set('ANT_GIT_HASH', git_hash) 73 73 version_conf.set('ANT_BUILD_DATE', build_date) 74 74
+164 -63
src/ant.c
··· 14 14 #include <unistd.h> 15 15 #include <libgen.h> 16 16 #include <sys/stat.h> 17 + #include <utarray.h> 17 18 18 19 #include "ant.h" 19 20 #include "config.h" ··· 90 91 coroutine_t *coro; 91 92 } async_exec_context_t; 92 93 94 + typedef struct { 95 + jsoff_t offset; 96 + jsoff_t size; 97 + uint8_t type; 98 + char detail[128]; 99 + } FreeListEntry; 100 + 101 + static UT_array *global_free_list = NULL; 102 + static jsoff_t protected_brk = 0; 103 + 104 + static const UT_icd free_list_icd = { 105 + .sz = sizeof(FreeListEntry), 106 + .init = NULL, 107 + .copy = NULL, 108 + .dtor = NULL, 109 + }; 110 + 93 111 static this_stack_t global_this_stack = {NULL, 0, 0}; 94 112 static call_stack_t global_call_stack = {NULL, 0, 0}; 95 113 static coroutine_queue_t pending_coroutines = {NULL, NULL}; ··· 120 138 121 139 static ant_library_t *library_registry = NULL; 122 140 static esm_module_cache_t global_module_cache = {NULL, 0}; 141 + 142 + void js_protect_init_memory(struct js *js) { 143 + protected_brk = js_getbrk(js); 144 + if (protected_brk < 0x2000) protected_brk = 0x2000; 145 + } 123 146 124 147 void ant_register_library(const char *name, ant_library_init_fn init_fn) { 125 148 ant_library_t *lib = (ant_library_t *)ANT_GC_MALLOC(sizeof(ant_library_t)); ··· 254 277 static jsval_t do_instanceof(struct js *js, jsval_t l, jsval_t r); 255 278 static jsval_t do_in(struct js *js, jsval_t l, jsval_t r); 256 279 static jsval_t resolveprop(struct js *js, jsval_t v); 280 + static jsoff_t free_list_allocate(size_t size); 257 281 static jsoff_t lkp(struct js *js, jsval_t obj, const char *buf, size_t len); 258 282 static jsval_t builtin_array_push(struct js *js, jsval_t *args, int nargs); 259 283 static jsval_t builtin_array_pop(struct js *js, jsval_t *args, int nargs); ··· 1005 1029 return true; 1006 1030 } 1007 1031 1032 + 1008 1033 static jsoff_t js_alloc(struct js *js, size_t size) { 1009 - jsoff_t ofs = js->brk; 1010 1034 size = align32((jsoff_t) size); 1035 + 1036 + jsoff_t ofs = free_list_allocate(size); 1037 + if (ofs != (jsoff_t) ~0) return ofs; 1038 + 1039 + ofs = js->brk; 1011 1040 if (js->brk + size > js->size) { 1012 1041 if (js_try_grow_memory(js, size)) { 1013 1042 ofs = js->brk; 1014 1043 if (js->brk + size > js->size) return ~(jsoff_t) 0; 1015 1044 } else { 1016 - // Call bdwgc collector instead of custom GC 1017 1045 ANT_GC_COLLECT(); 1018 - // Also compact our arena if needed 1019 1046 js_gc(js); 1020 1047 ofs = js->brk; 1021 1048 if (js->brk + size > js->size) { ··· 1028 1055 } 1029 1056 } 1030 1057 } 1058 + 1031 1059 js->brk += (jsoff_t) size; 1032 1060 return ofs; 1033 1061 } ··· 1117 1145 return t == T_OBJ || t == T_PROP || t == T_STR || t == T_FUNC || t == T_ARR || t == T_PROMISE; 1118 1146 } 1119 1147 1120 - // DISABLED: These functions were causing memory corruption by moving offsets 1121 - // Now we use bdwgc for C allocations and keep JS arena stable (no compacting) 1122 - /* 1123 - static void js_fixup_offsets(struct js *js, jsoff_t start, jsoff_t size) { 1124 - for (jsoff_t n, v, off = 0; off < js->brk; off += n) { 1125 - v = loadoff(js, off); 1126 - n = esize(v & ~(GCMASK | CONSTMASK)); 1127 - if (v & GCMASK) continue; 1128 - 1129 - jsoff_t flags = v & (GCMASK | CONSTMASK); 1130 - jsoff_t cleaned = v & ~(GCMASK | CONSTMASK); 1131 - if ((cleaned & 3) != T_OBJ && (cleaned & 3) != T_PROP) continue; 1132 - jsoff_t adjusted = cleaned > start ? cleaned - size : cleaned; 1133 - if (cleaned != adjusted) saveoff(js, off, adjusted | flags); 1134 - if ((cleaned & 3) == T_OBJ) { 1135 - jsoff_t u = loadoff(js, (jsoff_t) (off + sizeof(jsoff_t))); 1136 - if (u > start) saveoff(js, (jsoff_t) (off + sizeof(jsoff_t)), u - size); 1137 - } 1138 - if ((cleaned & 3) == T_PROP) { 1139 - jsoff_t koff = loadoff(js, (jsoff_t) (off + sizeof(off))); 1140 - if (koff > start) saveoff(js, (jsoff_t) (off + sizeof(off)), koff - size); 1141 - jsval_t val = loadval(js, (jsoff_t) (off + sizeof(off) + sizeof(off))); 1142 - if (is_mem_entity(vtype(val)) && vdata(val) > start) { 1143 - saveval(js, (jsoff_t) (off + sizeof(off) + sizeof(off)), mkval(vtype(val), (unsigned long) (vdata(val) - size))); 1144 - } 1145 - } 1146 - } 1147 - 1148 - jsoff_t off = (jsoff_t) vdata(js->scope); 1149 - if (off > start) js->scope = mkval(T_OBJ, off - size); 1150 - if (js->nogc >= start) js->nogc -= size; 1151 - if (js->code > (char *) js->mem && js->code - (char *) js->mem < js->size && js->code - (char *) js->mem > start) { 1152 - js->code -= size; 1153 - } 1154 - } 1155 - 1156 - static void js_delete_marked_entities(struct js *js) { 1157 - for (jsoff_t n, v, off = 0; off < js->brk; off += n) { 1158 - v = loadoff(js, off); 1159 - n = esize(v & ~(GCMASK | CONSTMASK)); 1160 - if (v & GCMASK) { 1161 - js_fixup_offsets(js, off, n); 1162 - memmove(&js->mem[off], &js->mem[off + n], js->brk - off - n); 1163 - js->brk -= n; 1164 - n = 0; 1165 - } 1166 - } 1167 - } 1168 - */ 1169 - 1170 1148 static void js_mark_all_entities_for_deletion(struct js *js) { 1171 1149 for (jsoff_t v, off = 0; off < js->brk; off += esize(v & ~(GCMASK | CONSTMASK))) { 1172 1150 v = loadoff(js, off); ··· 1199 1177 if (js->nogc) js_unmark_entity(js, js->nogc); 1200 1178 } 1201 1179 1180 + static void init_free_list(void) { 1181 + if (global_free_list == NULL) { 1182 + utarray_new(global_free_list, &free_list_icd); 1183 + } else { 1184 + utarray_clear(global_free_list); 1185 + } 1186 + } 1187 + 1188 + static void free_list_clear(void) { 1189 + if (global_free_list != NULL) utarray_clear(global_free_list); 1190 + } 1191 + 1192 + static void free_list_compact(void) { 1193 + unsigned int len = utarray_len(global_free_list); 1194 + if (len <= 1) return; 1195 + 1196 + FreeListEntry *entries = (FreeListEntry *)utarray_front(global_free_list); 1197 + 1198 + for (unsigned int i = 0; i < len - 1; i++) { 1199 + for (unsigned int j = 0; j < len - i - 1; j++) { 1200 + if (entries[j].offset > entries[j + 1].offset) { 1201 + FreeListEntry temp = entries[j]; 1202 + entries[j] = entries[j + 1]; 1203 + entries[j + 1] = temp; 1204 + } 1205 + } 1206 + } 1207 + 1208 + unsigned int write_pos = 0; 1209 + for (unsigned int i = 1; i < len; i++) { 1210 + if (entries[write_pos].offset + entries[write_pos].size == entries[i].offset) { 1211 + entries[write_pos].size += entries[i].size; 1212 + } else { 1213 + write_pos++; 1214 + entries[write_pos] = entries[i]; 1215 + } 1216 + } 1217 + 1218 + unsigned int final_count = write_pos + 1; 1219 + while (utarray_len(global_free_list) > final_count) { 1220 + utarray_pop_back(global_free_list); 1221 + } 1222 + } 1223 + 1224 + static jsoff_t free_list_zero_out(struct js *js) { 1225 + unsigned int len = utarray_len(global_free_list); 1226 + if (len == 0) return 0; 1227 + 1228 + jsoff_t total_freed = 0; 1229 + FreeListEntry *entries = (FreeListEntry *)utarray_front(global_free_list); 1230 + for (unsigned int i = 0; i < len; i++) { 1231 + if (entries[i].offset > 0 && entries[i].size > 0) { 1232 + if (entries[i].offset + entries[i].size > js->size) continue; 1233 + memset(&js->mem[entries[i].offset], 0, entries[i].size); 1234 + total_freed += entries[i].size; 1235 + } 1236 + } 1237 + 1238 + return total_freed; 1239 + } 1240 + 1241 + static jsoff_t free_list_allocate(size_t size) { 1242 + unsigned int len = utarray_len(global_free_list); 1243 + if (len == 0) return ~(jsoff_t) 0; 1244 + size = align32((jsoff_t) size); 1245 + 1246 + FreeListEntry *entries = (FreeListEntry *)utarray_front(global_free_list); 1247 + jsoff_t safe_reuse_threshold = protected_brk > 0 ? protected_brk + 0x8000 : 0x10000; 1248 + 1249 + for (unsigned int i = 0; i < len; i++) { 1250 + if (entries[i].offset >= safe_reuse_threshold && entries[i].size >= size) { 1251 + jsoff_t allocated_offset = entries[i].offset; 1252 + 1253 + entries[i].offset += size; 1254 + entries[i].size -= size; 1255 + 1256 + if (entries[i].size == 0) utarray_erase(global_free_list, i, 1); 1257 + return allocated_offset; 1258 + } 1259 + } 1260 + 1261 + return ~(jsoff_t) 0; 1262 + } 1263 + 1264 + static bool is_builtin_or_system(jsoff_t offset, struct js *js) { 1265 + jsval_t obj_val = mkval(T_OBJ, offset); 1266 + 1267 + jsoff_t native_off = lkp(js, obj_val, "__native_func", 13); 1268 + if (native_off != 0) return true; 1269 + 1270 + jsoff_t code_off = lkp(js, obj_val, "__code", 6); 1271 + if (code_off != 0) { 1272 + jsval_t code_val = resolveprop(js, mkval(T_PROP, code_off)); 1273 + if (vtype(code_val) == T_STR) { 1274 + jsoff_t slen, str_off = vstr(js, code_val, &slen); 1275 + if (slen > 10 && memcmp(&js->mem[str_off], "__builtin_", 10) == 0) return true; 1276 + } 1277 + } 1278 + 1279 + return false; 1280 + } 1281 + 1282 + static void free_list_add(jsoff_t offset, jsoff_t size, struct js *js) { 1283 + if (offset >= js->size || size == 0 || offset + size > js->size * 2) return; 1284 + if (protected_brk > 0) if (offset <= protected_brk) return; 1285 + 1286 + jsoff_t entity_val = loadoff(js, offset); 1287 + uint8_t entity_type = entity_val & 3; 1288 + 1289 + if (entity_type == T_OBJ && is_builtin_or_system(offset, js)) return; 1290 + if (entity_type == T_PROP) return; 1291 + if (entity_type == T_STR && offset < 0x1000) return; 1292 + 1293 + FreeListEntry entry = {0}; 1294 + entry.offset = offset; 1295 + entry.size = size; 1296 + entry.type = entity_type; 1297 + entry.detail[0] = '\0'; 1298 + 1299 + utarray_push_back(global_free_list, &entry); 1300 + } 1301 + 1202 1302 static void js_clear_gc_marks(struct js *js) { 1203 - // Simply clear all GC marks without moving memory 1204 1303 for (jsoff_t v, off = 0; off < js->brk; off += esize(v & ~(GCMASK | CONSTMASK))) { 1205 1304 v = loadoff(js, off); 1206 1305 if (v & GCMASK) { 1207 - // Keep the entity but clear the GC mark 1306 + jsoff_t size = esize(v & ~(GCMASK | CONSTMASK)); 1307 + free_list_add(off, size, js); 1208 1308 saveoff(js, off, v & ~GCMASK); 1209 1309 } 1210 1310 } 1211 1311 } 1212 1312 1213 - void js_gc(struct js *js) { 1313 + jsoff_t js_gc(struct js *js) { 1214 1314 setlwm(js); 1215 - if (js->nogc == (jsoff_t) ~0) return; 1216 - 1217 - // NOTE: We still do mark-and-sweep to identify live objects, 1218 - // but we NO LONGER compact/move memory. This prevents offset corruption. 1219 - // The bdwgc handles C-side allocations, and we keep the JS arena stable. 1315 + if (js->nogc == (jsoff_t) ~0) return 0; 1220 1316 1221 1317 js_mark_all_entities_for_deletion(js); 1222 1318 js_unmark_used_entities(js); 1223 - // REMOVED: js_delete_marked_entities(js); // This was causing offset corruption 1224 1319 1225 - // Instead, we just clear the GC marks without moving anything 1226 1320 js_clear_gc_marks(js); 1321 + free_list_compact(); 1322 + 1323 + jsoff_t freed = free_list_zero_out(js); 1324 + if (global_free_list != NULL) utarray_clear(global_free_list); 1325 + 1326 + return freed; 1227 1327 } 1228 1328 1229 1329 static jsoff_t skiptonext(const char *code, jsoff_t len, jsoff_t n) { ··· 7790 7890 7791 7891 struct js *js_create(void *buf, size_t len) { 7792 7892 ANT_GC_INIT(); 7893 + init_free_list(); 7793 7894 7794 7895 struct js *js = NULL; 7795 7896 if (len < sizeof(*js) + esize(T_OBJ)) return js;
+2
src/main.c
··· 138 138 ant_register_library("ant:fs", fs_library); 139 139 ant_register_library("ant:shell", shell_library); 140 140 ant_register_library("ant:path", path_library); 141 + 142 + js_protect_init_memory(js); 141 143 142 144 if (repl_mode) ant_repl_run(); else { 143 145 js_result = execute_module(js, module_file);
+2 -1
src/modules/builtin.c
··· 66 66 (void) args; (void) nargs; 67 67 size_t before_brk = js_getbrk(js); 68 68 69 - js_gc(js); 69 + uint32_t freed = js_gc(js); 70 70 size_t after_brk = js_getbrk(js); 71 71 72 72 jsval_t result = js_mkobj(js); 73 73 js_set(js, result, "before", js_mknum((double)before_brk)); 74 74 js_set(js, result, "after", js_mknum((double)after_brk)); 75 + js_set(js, result, "freed", js_mknum((double)freed)); 75 76 76 77 return result; 77 78 }
+4 -4
tests/test_gc.cjs
··· 32 32 33 33 let gcResult = Ant.gc(); 34 34 console.log("GC result:"); 35 - console.log(" before:", gcResult.before); 36 - console.log(" after:", gcResult.after); 37 - console.log(" freed:", gcResult.before - gcResult.after); 35 + console.log(" before break:", gcResult.before); 36 + console.log(" after break:", gcResult.after); 37 + console.log(" freed bytes:", gcResult.freed); 38 38 39 39 console.log("\n=== Verifying memory after GC ==="); 40 40 let alloc3 = Ant.alloc(); ··· 58 58 // Clear and collect 59 59 temp = null; 60 60 let gc = Ant.gc(); 61 - console.log(" After GC - used:", gc.after, "freed:", gc.before - gc.after); 61 + console.log(" After GC - used:", gc.after, "freed bytes:", gc.freed); 62 62 } 63 63 64 64 console.log("\n=== Testing stats consistency ===");