visaljs 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. package/README.md +63 -2
  2. package/THIRD_PARTY_LICENSES +1216 -0
  3. package/cli.js +4414 -325
  4. package/node/include/js_node.h +57 -1
  5. package/node/include/js_uv.h +8 -1
  6. package/node/prelude/_stream_duplex.js +4 -0
  7. package/node/prelude/_stream_passthrough.js +4 -0
  8. package/node/prelude/_stream_readable.js +4 -0
  9. package/node/prelude/_stream_transform.js +4 -0
  10. package/node/prelude/_stream_writable.js +4 -0
  11. package/node/prelude/assert.js +1334 -0
  12. package/node/prelude/assert_strict.js +12 -0
  13. package/node/prelude/async_hooks.js +1 -1
  14. package/node/prelude/buffer.js +43 -3
  15. package/node/prelude/child_process.js +1098 -0
  16. package/node/prelude/cluster.js +52 -0
  17. package/node/prelude/console.js +328 -0
  18. package/node/prelude/constants.js +10 -0
  19. package/node/prelude/crypto.js +1 -1
  20. package/node/prelude/diagnostics_channel.js +1 -1
  21. package/node/prelude/dns.js +1 -1
  22. package/node/prelude/domain.js +115 -0
  23. package/node/prelude/events.js +144 -16
  24. package/node/prelude/fs.js +1138 -151
  25. package/node/prelude/fs_promises.js +117 -11
  26. package/node/prelude/http.js +104 -27
  27. package/node/prelude/http2.js +1 -1
  28. package/node/prelude/https.js +148 -0
  29. package/node/prelude/inspector.js +27 -0
  30. package/node/prelude/inspector_promises.js +4 -0
  31. package/node/prelude/internal/async_context.js +1 -1
  32. package/node/prelude/internal/buffer_transcode.js +44 -0
  33. package/node/prelude/internal/comparisons.js +916 -0
  34. package/node/prelude/internal/compression_streams.js +153 -0
  35. package/node/prelude/internal/crypto/asym.js +27 -14
  36. package/node/prelude/internal/crypto/cipher.js +16 -5
  37. package/node/prelude/internal/crypto/diffiehellman.js +3 -2
  38. package/node/prelude/internal/crypto/hash.js +2 -1
  39. package/node/prelude/internal/crypto/kdf.js +14 -9
  40. package/node/prelude/internal/crypto/keys.js +63 -50
  41. package/node/prelude/internal/crypto/random.js +2 -1
  42. package/node/prelude/internal/crypto/util.js +4 -4
  43. package/node/prelude/internal/dotenv.js +98 -0
  44. package/node/prelude/internal/errors.js +21 -5
  45. package/node/prelude/internal/events_extras.js +116 -0
  46. package/node/prelude/internal/events_on.js +100 -0
  47. package/node/prelude/internal/features.js +55 -0
  48. package/node/prelude/internal/fetch.js +364 -0
  49. package/node/prelude/internal/form_data.js +376 -0
  50. package/node/prelude/internal/fs_bigint_stats.js +41 -0
  51. package/node/prelude/internal/fs_blob.js +19 -0
  52. package/node/prelude/internal/fs_cp.js +709 -0
  53. package/node/prelude/internal/fs_dir.js +166 -0
  54. package/node/prelude/internal/fs_filehandle.js +287 -0
  55. package/node/prelude/internal/fs_glob.js +296 -0
  56. package/node/prelude/internal/fs_statfs.js +42 -0
  57. package/node/prelude/internal/fs_stats.js +89 -0
  58. package/node/prelude/internal/fs_streams.js +541 -0
  59. package/node/prelude/internal/fs_utils.js +286 -0
  60. package/node/prelude/internal/fs_watch.js +580 -0
  61. package/node/prelude/internal/http_agent.js +26 -0
  62. package/node/prelude/internal/mime.js +249 -0
  63. package/node/prelude/internal/net.js +1 -0
  64. package/node/prelude/internal/parse_args.js +288 -0
  65. package/node/prelude/internal/perf_histogram.js +435 -0
  66. package/node/prelude/internal/perf_observe.js +119 -0
  67. package/node/prelude/internal/performance_global.js +19 -0
  68. package/node/prelude/internal/process_events.js +49 -1
  69. package/node/prelude/internal/process_memory.js +14 -0
  70. package/node/prelude/internal/process_more.js +414 -0
  71. package/node/prelude/internal/process_signals.js +2 -1
  72. package/node/prelude/internal/process_stdin.js +52 -0
  73. package/node/prelude/internal/process_stdio.js +36 -0
  74. package/node/prelude/internal/querystring.js +0 -0
  75. package/node/prelude/internal/readline.js +338 -0
  76. package/node/prelude/internal/socketaddress.js +8 -4
  77. package/node/prelude/internal/source_map.js +122 -0
  78. package/node/prelude/internal/stream_compose.js +74 -0
  79. package/node/prelude/internal/stream_from.js +6 -0
  80. package/node/prelude/internal/stream_operators.js +23 -0
  81. package/node/prelude/internal/stream_pipeline.js +7 -0
  82. package/node/prelude/internal/stream_transform.js +10 -0
  83. package/node/prelude/internal/stream_web_adapters.js +143 -0
  84. package/node/prelude/internal/streams/add_abort_signal.js +58 -0
  85. package/node/prelude/internal/streams/compose.js +244 -0
  86. package/node/prelude/internal/streams/destroy.js +358 -0
  87. package/node/prelude/internal/streams/duplex.js +199 -0
  88. package/node/prelude/internal/streams/duplexify.js +378 -0
  89. package/node/prelude/internal/streams/end_of_stream.js +387 -0
  90. package/node/prelude/internal/streams/errors.js +216 -0
  91. package/node/prelude/internal/streams/from.js +211 -0
  92. package/node/prelude/internal/streams/legacy.js +120 -0
  93. package/node/prelude/internal/streams/operators.js +375 -0
  94. package/node/prelude/internal/streams/passthrough.js +45 -0
  95. package/node/prelude/internal/streams/pipeline.js +468 -0
  96. package/node/prelude/internal/streams/readable.js +1835 -0
  97. package/node/prelude/internal/streams/state.js +48 -0
  98. package/node/prelude/internal/streams/transform.js +203 -0
  99. package/node/prelude/internal/streams/utils.js +355 -0
  100. package/node/prelude/internal/streams/validators.js +108 -0
  101. package/node/prelude/internal/streams/writable.js +1160 -0
  102. package/node/prelude/internal/structured_clone.js +238 -0
  103. package/node/prelude/internal/system_errors.js +37 -0
  104. package/node/prelude/internal/test_runner/mock.js +431 -0
  105. package/node/prelude/internal/test_runner/reporters.js +404 -0
  106. package/node/prelude/internal/test_runner/test.js +1571 -0
  107. package/node/prelude/internal/timers.js +38 -7
  108. package/node/prelude/internal/timers_global.js +1 -1
  109. package/node/prelude/internal/tls/common.js +8 -2
  110. package/node/prelude/internal/tls/wrap.js +3 -15
  111. package/node/prelude/internal/tls/x509.js +1 -1
  112. package/node/prelude/internal/tty_read_stream.js +39 -0
  113. package/node/prelude/internal/tty_streams.js +3 -0
  114. package/node/prelude/internal/util_debuglog.js +68 -0
  115. package/node/prelude/internal/util_diff.js +63 -0
  116. package/node/prelude/internal/util_more.js +111 -0
  117. package/node/prelude/internal/util_styles.js +76 -0
  118. package/node/prelude/internal/zlib_engines.js +1 -1
  119. package/node/prelude/module.js +194 -7
  120. package/node/prelude/net.js +2 -1
  121. package/node/prelude/os.js +88 -11
  122. package/node/prelude/path.js +11 -7
  123. package/node/prelude/path_posix.js +1 -1
  124. package/node/prelude/path_win32.js +1 -1
  125. package/node/prelude/perf_hooks.js +147 -0
  126. package/node/prelude/process.js +36 -1
  127. package/node/prelude/punycode.js +188 -0
  128. package/node/prelude/querystring.js +1 -1
  129. package/node/prelude/readline.js +8 -0
  130. package/node/prelude/readline_promises.js +88 -0
  131. package/node/prelude/repl.js +38 -0
  132. package/node/prelude/sea.js +23 -0
  133. package/node/prelude/stream.js +70 -1332
  134. package/node/prelude/stream_consumers.js +34 -0
  135. package/node/prelude/stream_promises.js +1 -1
  136. package/node/prelude/stream_web.js +15 -2
  137. package/node/prelude/string_decoder.js +1 -1
  138. package/node/prelude/sys.js +7 -0
  139. package/node/prelude/test.js +56 -0
  140. package/node/prelude/test_reporters.js +269 -0
  141. package/node/prelude/timers.js +7 -84
  142. package/node/prelude/timers_promises.js +1 -1
  143. package/node/prelude/tls.js +2 -1
  144. package/node/prelude/trace_events.js +52 -0
  145. package/node/prelude/tty.js +10 -4
  146. package/node/prelude/url.js +88 -27
  147. package/node/prelude/util.js +121 -5
  148. package/node/prelude/util_types.js +4 -3
  149. package/node/prelude/v8.js +814 -0
  150. package/node/prelude/vm.js +86 -0
  151. package/node/prelude/wasi.js +10 -0
  152. package/node/prelude/worker_threads.js +56 -0
  153. package/node/prelude/zlib.js +58 -25
  154. package/node/src/host_brotli.c +59 -11
  155. package/node/src/host_fs.c +342 -11
  156. package/node/src/host_fswatch.c +170 -0
  157. package/node/src/host_http.c +143 -20
  158. package/node/src/host_loop.c +25 -2
  159. package/node/src/host_net.c +55 -7
  160. package/node/src/host_os.c +55 -1
  161. package/node/src/host_proc.c +291 -0
  162. package/node/src/host_process.c +488 -0
  163. package/node/src/host_sys.c +64 -2
  164. package/node/src/host_zlib.c +27 -7
  165. package/node/src/host_zstd.c +41 -6
  166. package/node/third_party/hdrhistogram/LICENSE +42 -0
  167. package/node/third_party/hdrhistogram/README.md +5 -0
  168. package/node/third_party/libuv/LICENSE +0 -0
  169. package/node/third_party/libuv/LICENSE-extra +36 -0
  170. package/node/third_party/libuv/include/uv/aix.h +0 -0
  171. package/node/third_party/libuv/include/uv/bsd.h +0 -0
  172. package/node/third_party/libuv/include/uv/darwin.h +0 -0
  173. package/node/third_party/libuv/include/uv/errno.h +0 -0
  174. package/node/third_party/libuv/include/uv/linux.h +0 -0
  175. package/node/third_party/libuv/include/uv/os390.h +0 -0
  176. package/node/third_party/libuv/include/uv/posix.h +0 -0
  177. package/node/third_party/libuv/include/uv/sunos.h +0 -0
  178. package/node/third_party/libuv/include/uv/threadpool.h +0 -0
  179. package/node/third_party/libuv/include/uv/tree.h +0 -0
  180. package/node/third_party/libuv/include/uv/unix.h +0 -0
  181. package/node/third_party/libuv/include/uv/version.h +0 -0
  182. package/node/third_party/libuv/include/uv/win.h +0 -0
  183. package/node/third_party/libuv/include/uv.h +0 -0
  184. package/node/third_party/libuv/src/fs-poll.c +0 -0
  185. package/node/third_party/libuv/src/heap-inl.h +0 -0
  186. package/node/third_party/libuv/src/idna.c +0 -0
  187. package/node/third_party/libuv/src/idna.h +0 -0
  188. package/node/third_party/libuv/src/inet.c +0 -0
  189. package/node/third_party/libuv/src/queue.h +0 -0
  190. package/node/third_party/libuv/src/random.c +0 -0
  191. package/node/third_party/libuv/src/strscpy.c +0 -0
  192. package/node/third_party/libuv/src/strscpy.h +0 -0
  193. package/node/third_party/libuv/src/strtok.c +0 -0
  194. package/node/third_party/libuv/src/strtok.h +0 -0
  195. package/node/third_party/libuv/src/thread-common.c +0 -0
  196. package/node/third_party/libuv/src/threadpool.c +0 -0
  197. package/node/third_party/libuv/src/timer.c +0 -0
  198. package/node/third_party/libuv/src/unix/async.c +0 -0
  199. package/node/third_party/libuv/src/unix/bsd-ifaddrs.c +0 -0
  200. package/node/third_party/libuv/src/unix/core.c +0 -0
  201. package/node/third_party/libuv/src/unix/darwin-proctitle.c +0 -0
  202. package/node/third_party/libuv/src/unix/darwin-stub.h +0 -0
  203. package/node/third_party/libuv/src/unix/darwin-syscalls.h +0 -0
  204. package/node/third_party/libuv/src/unix/darwin.c +0 -0
  205. package/node/third_party/libuv/src/unix/dl.c +0 -0
  206. package/node/third_party/libuv/src/unix/fs.c +0 -0
  207. package/node/third_party/libuv/src/unix/fsevents.c +0 -0
  208. package/node/third_party/libuv/src/unix/getaddrinfo.c +0 -0
  209. package/node/third_party/libuv/src/unix/getnameinfo.c +0 -0
  210. package/node/third_party/libuv/src/unix/internal.h +0 -0
  211. package/node/third_party/libuv/src/unix/kqueue.c +0 -0
  212. package/node/third_party/libuv/src/unix/linux.c +0 -0
  213. package/node/third_party/libuv/src/unix/loop-watcher.c +0 -0
  214. package/node/third_party/libuv/src/unix/loop.c +0 -0
  215. package/node/third_party/libuv/src/unix/pipe.c +0 -0
  216. package/node/third_party/libuv/src/unix/poll.c +0 -0
  217. package/node/third_party/libuv/src/unix/process.c +0 -0
  218. package/node/third_party/libuv/src/unix/procfs-exepath.c +0 -0
  219. package/node/third_party/libuv/src/unix/proctitle.c +0 -0
  220. package/node/third_party/libuv/src/unix/random-devurandom.c +0 -0
  221. package/node/third_party/libuv/src/unix/random-getentropy.c +0 -0
  222. package/node/third_party/libuv/src/unix/random-getrandom.c +0 -0
  223. package/node/third_party/libuv/src/unix/random-sysctl-linux.c +0 -0
  224. package/node/third_party/libuv/src/unix/signal.c +0 -0
  225. package/node/third_party/libuv/src/unix/stream.c +0 -0
  226. package/node/third_party/libuv/src/unix/tcp.c +0 -0
  227. package/node/third_party/libuv/src/unix/thread.c +0 -0
  228. package/node/third_party/libuv/src/unix/tty.c +0 -0
  229. package/node/third_party/libuv/src/unix/udp.c +0 -0
  230. package/node/third_party/libuv/src/uv-common.c +0 -0
  231. package/node/third_party/libuv/src/uv-common.h +0 -0
  232. package/node/third_party/libuv/src/uv-data-getter-setters.c +0 -0
  233. package/node/third_party/libuv/src/version.c +0 -0
  234. package/node/third_party/llhttp/LICENSE +0 -0
  235. package/node/third_party/llhttp/api.c +0 -0
  236. package/node/third_party/llhttp/http.c +0 -0
  237. package/node/third_party/llhttp/llhttp.c +0 -0
  238. package/node/third_party/llhttp/llhttp.h +0 -0
  239. package/node/third_party/nodejs/LICENSE +47 -0
  240. package/node/third_party/nodejs/README.md +5 -0
  241. package/node/third_party/parseargs/LICENSE +21 -0
  242. package/node/third_party/parseargs/README.md +5 -0
  243. package/node/third_party/punycode/LICENSE +20 -0
  244. package/node/third_party/punycode/README.md +5 -0
  245. package/node/third_party/zlib/LICENSE.chromium +27 -0
  246. package/package.json +1 -5
  247. package/prebuilt/darwin-arm64/boringssl-4a47438eaf7e73ba.a +0 -0
  248. package/prebuilt/darwin-arm64/brotli-7995bbdacc52e51c.a +0 -0
  249. package/prebuilt/darwin-arm64/uv-72ab666eae197cd8.a +0 -0
  250. package/prebuilt/darwin-arm64/zlib-4ce2317a4c3f5fb5.a +0 -0
  251. package/prebuilt/darwin-arm64/zstd-015315ac7c784c73.a +0 -0
  252. package/prebuilt/darwin-x64/boringssl-4a47438eaf7e73ba.a +0 -0
  253. package/prebuilt/darwin-x64/brotli-7995bbdacc52e51c.a +0 -0
  254. package/prebuilt/darwin-x64/uv-72ab666eae197cd8.a +0 -0
  255. package/prebuilt/darwin-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
  256. package/prebuilt/darwin-x64/zstd-015315ac7c784c73.a +0 -0
  257. package/prebuilt/linux-arm64/boringssl-4a47438eaf7e73ba.a +0 -0
  258. package/prebuilt/linux-arm64/brotli-65481ff838ce9def.a +0 -0
  259. package/prebuilt/linux-arm64/uv-f1353f50ff983f3c.a +0 -0
  260. package/prebuilt/linux-arm64/zlib-4ce2317a4c3f5fb5.a +0 -0
  261. package/prebuilt/linux-arm64/zstd-015315ac7c784c73.a +0 -0
  262. package/prebuilt/linux-arm64-musl/boringssl-4a47438eaf7e73ba.a +0 -0
  263. package/prebuilt/linux-arm64-musl/brotli-65481ff838ce9def.a +0 -0
  264. package/prebuilt/linux-arm64-musl/uv-f1353f50ff983f3c.a +0 -0
  265. package/prebuilt/linux-arm64-musl/zlib-4ce2317a4c3f5fb5.a +0 -0
  266. package/prebuilt/linux-arm64-musl/zstd-015315ac7c784c73.a +0 -0
  267. package/prebuilt/linux-x64/boringssl-4a47438eaf7e73ba.a +0 -0
  268. package/prebuilt/linux-x64/brotli-65481ff838ce9def.a +0 -0
  269. package/prebuilt/linux-x64/uv-f1353f50ff983f3c.a +0 -0
  270. package/prebuilt/linux-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
  271. package/prebuilt/linux-x64/zstd-015315ac7c784c73.a +0 -0
  272. package/prebuilt/linux-x64-musl/boringssl-4a47438eaf7e73ba.a +0 -0
  273. package/prebuilt/linux-x64-musl/brotli-65481ff838ce9def.a +0 -0
  274. package/prebuilt/linux-x64-musl/uv-f1353f50ff983f3c.a +0 -0
  275. package/prebuilt/linux-x64-musl/zlib-4ce2317a4c3f5fb5.a +0 -0
  276. package/prebuilt/linux-x64-musl/zstd-015315ac7c784c73.a +0 -0
  277. package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
  278. package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
  279. package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
  280. package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
  281. package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
  282. package/runtime/include/js_atoms.gen.h +0 -0
  283. package/runtime/include/js_atoms.h +1 -1
  284. package/runtime/include/js_bigint.h +1 -1
  285. package/runtime/include/js_buffer.h +4 -4
  286. package/runtime/include/js_builtins.h +41 -3
  287. package/runtime/include/js_console.h +9 -4
  288. package/runtime/include/js_convert.h +1 -1
  289. package/runtime/include/js_core.def +5 -4
  290. package/runtime/include/js_crypto.h +1 -1
  291. package/runtime/include/js_error.h +1 -1
  292. package/runtime/include/js_exception.h +21 -1
  293. package/runtime/include/js_function.h +33 -9
  294. package/runtime/include/js_gc.h +172 -30
  295. package/runtime/include/js_generator.h +2 -2
  296. package/runtime/include/js_host.h +19 -1
  297. package/runtime/include/js_jobs.h +5 -5
  298. package/runtime/include/js_module.h +1 -1
  299. package/runtime/include/js_object.h +142 -15
  300. package/runtime/include/js_ops.h +107 -5
  301. package/runtime/include/js_promise.h +3 -3
  302. package/runtime/include/js_regexp.h +1 -1
  303. package/runtime/include/js_runtime.h +0 -0
  304. package/runtime/include/js_symbol.h +1 -1
  305. package/runtime/include/js_web.h +2 -1
  306. package/runtime/include/jscell.h +22 -15
  307. package/runtime/include/jscontext.h +74 -39
  308. package/runtime/include/jsnum.h +1 -1
  309. package/runtime/include/jsstring.h +19 -6
  310. package/runtime/include/jsvalue.h +7 -3
  311. package/runtime/src/arguments.c +2 -2
  312. package/runtime/src/array.c +4 -1
  313. package/runtime/src/arraybuffer.c +19 -7
  314. package/runtime/src/async_iter.c +4 -3
  315. package/runtime/src/atoms.c +1 -1
  316. package/runtime/src/base64.c +2 -2
  317. package/runtime/src/bigint.c +1 -1
  318. package/runtime/src/blob.c +22 -9
  319. package/runtime/src/boringssl_support.cc +1 -1
  320. package/runtime/src/buffer.c +299 -91
  321. package/runtime/src/builtin_table.c +1 -1
  322. package/runtime/src/builtins.c +30 -4
  323. package/runtime/src/builtins_util.h +0 -0
  324. package/runtime/src/collections.c +18 -3
  325. package/runtime/src/components.c +1 -1
  326. package/runtime/src/console.c +14 -7
  327. package/runtime/src/convert.c +0 -0
  328. package/runtime/src/core_all.c +1 -1
  329. package/runtime/src/crypto.c +1 -1
  330. package/runtime/src/crypto_asym.c +1 -1
  331. package/runtime/src/crypto_bn.c +1 -1
  332. package/runtime/src/crypto_cipher.c +10 -3
  333. package/runtime/src/crypto_hash.c +1 -1
  334. package/runtime/src/crypto_host.c +1 -1
  335. package/runtime/src/crypto_internal.h +1 -1
  336. package/runtime/src/crypto_kdf.c +1 -1
  337. package/runtime/src/crypto_key.c +14 -1
  338. package/runtime/src/crypto_pkey.c +1 -1
  339. package/runtime/src/crypto_subtle.c +142 -58
  340. package/runtime/src/crypto_tls.c +1 -1
  341. package/runtime/src/date.c +5 -2
  342. package/runtime/src/domexception.c +2 -2
  343. package/runtime/src/elements.c +56 -17
  344. package/runtime/src/elements.h +7 -2
  345. package/runtime/src/encoding.c +1 -1
  346. package/runtime/src/error.c +4 -4
  347. package/runtime/src/events.c +23 -14
  348. package/runtime/src/exception.c +1 -1
  349. package/runtime/src/fetch.c +241 -17
  350. package/runtime/src/function.c +31 -19
  351. package/runtime/src/gc.c +1716 -207
  352. package/runtime/src/gc_visit.h +87 -0
  353. package/runtime/src/generator.c +5 -3
  354. package/runtime/src/global.c +14 -6
  355. package/runtime/src/headers.c +10 -10
  356. package/runtime/src/inspect.c +76 -17
  357. package/runtime/src/internal.h +5 -2
  358. package/runtime/src/iterate.c +94 -2
  359. package/runtime/src/iterator.c +1 -1
  360. package/runtime/src/jobs.c +11 -5
  361. package/runtime/src/jsnum.c +2 -2
  362. package/runtime/src/json.c +1 -1
  363. package/runtime/src/math.c +9 -1
  364. package/runtime/src/module.c +3 -3
  365. package/runtime/src/number.c +0 -0
  366. package/runtime/src/object.c +107 -74
  367. package/runtime/src/ops.c +21 -3
  368. package/runtime/src/os.c +192 -1
  369. package/runtime/src/os.h +33 -2
  370. package/runtime/src/process.c +73 -8
  371. package/runtime/src/promise.c +4 -4
  372. package/runtime/src/proxy.c +1 -1
  373. package/runtime/src/reflect.c +1 -1
  374. package/runtime/src/regexp.c +134 -2
  375. package/runtime/src/regexp_anchor.h +1 -1
  376. package/runtime/src/regexp_message.h +1 -1
  377. package/runtime/src/shape.c +10 -19
  378. package/runtime/src/shape.h +0 -0
  379. package/runtime/src/streams.c +22 -8
  380. package/runtime/src/streams.h +0 -0
  381. package/runtime/src/string.c +64 -25
  382. package/runtime/src/string_builtins.c +7 -7
  383. package/runtime/src/symbol.c +2 -7
  384. package/runtime/src/test_host.c +3 -3
  385. package/runtime/src/tls_roots.gen.h +102 -620
  386. package/runtime/src/typedarray.c +8 -3
  387. package/runtime/src/tznames.gen.h +2 -1
  388. package/runtime/src/uri.c +1 -1
  389. package/runtime/src/url.c +42 -39
  390. package/runtime/src/weak.c +15 -12
  391. package/runtime/src/web.c +21 -3
  392. package/runtime/src/wrapper.c +0 -0
  393. package/runtime/src/writable.c +7 -6
  394. package/runtime/third_party/ada-idna/LICENSE-MIT +0 -0
  395. package/runtime/third_party/ada-idna/README.md +7 -7
  396. package/runtime/third_party/ada-idna/convert.mjs +89 -27
  397. package/runtime/third_party/ada-idna/idna.c +30 -31
  398. package/runtime/third_party/ada-idna/idna.h +0 -0
  399. package/runtime/third_party/ada-idna/idna_tables.h +2272 -1952
  400. package/runtime/third_party/boringssl/third_party/fiat/LICENSE +15 -0
  401. package/runtime/third_party/mozilla-ca/LICENSE +373 -0
  402. package/runtime/third_party/mozilla-ca/README.md +9 -0
  403. package/runtime/third_party/nodejs/LICENSE +47 -0
  404. package/runtime/third_party/nodejs/README.md +7 -0
  405. package/runtime/third_party/quickjs-dtoa/LICENSE +0 -0
  406. package/runtime/third_party/quickjs-dtoa/README.md +1 -1
  407. package/runtime/third_party/quickjs-dtoa/dtoa.c +0 -0
  408. package/runtime/third_party/quickjs-dtoa/dtoa.h +0 -0
  409. package/runtime/third_party/quickjs-dtoa/dtoa_compat.h +0 -0
  410. package/runtime/third_party/quickjs-regexp/LICENSE +0 -0
  411. package/runtime/third_party/quickjs-regexp/README.md +2 -2
  412. package/runtime/third_party/quickjs-regexp/cutils.c +0 -0
  413. package/runtime/third_party/quickjs-regexp/cutils.h +0 -0
  414. package/runtime/third_party/quickjs-regexp/libregexp-opcode.h +0 -0
  415. package/runtime/third_party/quickjs-regexp/libregexp.c +0 -0
  416. package/runtime/third_party/quickjs-regexp/libregexp.h +0 -0
  417. package/runtime/third_party/quickjs-regexp/libunicode-table.h +0 -0
  418. package/runtime/third_party/quickjs-regexp/libunicode.c +0 -0
  419. package/runtime/third_party/quickjs-regexp/libunicode.h +0 -0
  420. package/runtime/third_party/unicode/LICENSE +39 -0
  421. package/runtime/third_party/unicode/README.md +9 -0
  422. package/runtime/third_party/v8-ieee754/LICENSE +0 -0
  423. package/runtime/third_party/v8-ieee754/LICENSE.fdlibm +10 -0
  424. package/runtime/third_party/v8-ieee754/README.md +3 -2
  425. package/runtime/third_party/v8-ieee754/convert.py +0 -0
  426. package/runtime/third_party/v8-ieee754/ieee754.c +0 -0
  427. package/runtime/third_party/v8-ieee754/ieee754.h +0 -0
  428. package/runtime/tools/regexp_compile.c +2 -2
package/runtime/src/gc.c CHANGED
@@ -1,12 +1,11 @@
1
- /* Heap and garbage collector (ADR 0002).
1
+ /* Heap and garbage collector.
2
2
  *
3
3
  * Allocation bits say which cells hold a value. They are exact whenever the
4
4
  * world is stopped:
5
5
  * - sweeping a block sets them to its mark bits (only survivors);
6
- * - when a block becomes the allocation block of its class, every cell's
7
- * bit is set, because the allocator may hand out any of them;
8
- * - stopping the world clears the bits of the cells still on a free list.
9
- * Only the allocation block of a class has cells on a free list.
6
+ * - the allocator sets the bits of a word's free cells when it takes the
7
+ * word, since it may hand out any of them;
8
+ * - stopping the world clears the bits of the cells it didn't hand out.
10
9
  */
11
10
  #include "js_gc.h"
12
11
 
@@ -21,6 +20,16 @@
21
20
  #include "jscontext.h"
22
21
  #include "os.h"
23
22
 
23
+ #if defined(__clang__) || defined(__GNUC__)
24
+ #define JS_NOINLINE __attribute__((noinline))
25
+ #define JS_ALWAYS_INLINE inline __attribute__((always_inline))
26
+ #define JS_NO_ASAN __attribute__((no_sanitize_address))
27
+ #else
28
+ #define JS_NOINLINE __declspec(noinline)
29
+ #define JS_ALWAYS_INLINE __forceinline
30
+ #define JS_NO_ASAN
31
+ #endif
32
+
24
33
  const uint16_t js_size_class_bytes[JS_NUM_SIZE_CLASSES] = {
25
34
  16, 32, 48, 64, 80, 96, 112, 128,
26
35
  160, 192, 224, 256,
@@ -31,10 +40,29 @@ const uint16_t js_size_class_bytes[JS_NUM_SIZE_CLASSES] = {
31
40
  5120, 6144, 7168, 8192,
32
41
  };
33
42
 
43
+ /* Edges wait in a FIFO this long while their cells are prefetched (a power of two). */
44
+ #define MARK_FIFO 32
45
+
46
+ /* A marker: its own mark stack, so several can mark at once. Visit
47
+ * functions only read their cell and call js_trace_value / js_trace_cell. */
34
48
  struct JSTracer {
35
- JSHeap *heap;
36
- size_t sp; /* mark stack depth */
49
+ JSHeap *heap;
50
+ JSCell **stack; /* the mark stack: marked cells to visit; grows, and goes back to the heap */
51
+ size_t sp, cap;
52
+ size_t base; /* marking with others: the stack is [base, sp), given away from the bottom */
53
+ JSCell *fifo[MARK_FIFO]; /* edges not marked yet, their cells being prefetched */
54
+ unsigned fifo_head, fifo_count;
37
55
  const void *stack_low; /* the conservative scan covers [stack_low, stack_base) */
56
+ size_t marked; /* cells this tracer marked */
57
+ size_t marked_bytes;
58
+ JSCell *partial; /* incremental marking: a large cell visited a chunk at a time */
59
+ size_t partial_from; /* the byte of it the next chunk starts at */
60
+ bool parallel; /* other markers too: mark bits are set atomically */
61
+ #ifdef JS_GC_VERIFY
62
+ const JSCell *visiting; /* the cell whose children are being traced (NULL: a root) */
63
+ bool check_old; /* verify_remembered: check the edges of old cells, don't mark */
64
+ bool check_black; /* verify_marking: the same check, after an incremental marking */
65
+ #endif
38
66
  };
39
67
 
40
68
  _Noreturn void js_fatal(const char *fmt, ...) {
@@ -55,7 +83,7 @@ static void *xrealloc(void *p, size_t size) {
55
83
  return q;
56
84
  }
57
85
 
58
- /* ADR 0001: every cell address must fit in 48 bits. */
86
+ /* Every cell address must fit in 48 bits: a value holds a cell pointer in its low 48. */
59
87
  static void check_address(const void *p, size_t size) {
60
88
  if ((uintptr_t)p + size > (UINT64_C(1) << 48))
61
89
  js_fatal("heap memory mapped above 2^48 (%p)", p);
@@ -63,14 +91,9 @@ static void check_address(const void *p, size_t size) {
63
91
 
64
92
  /* ---- Cell classes ------------------------------------------------------ */
65
93
 
66
- static void value_array_visit(JSCell *c, JSTracer *trc) {
67
- JSValueArray *a = (JSValueArray *)c;
68
- for (uint32_t i = 0; i < c->aux; i++) js_trace_value(trc, a->values[i]);
69
- }
94
+ static const JSCellClass value_array_class = { "ValueArray", NULL, NULL, NULL }; /* gc_visit.h visits it */
70
95
 
71
- static const JSCellClass value_array_class = { "ValueArray", value_array_visit, NULL };
72
-
73
- static const JSCellClass bigint_class = { "BigInt", NULL, NULL }; /* limbs only (ADR 0016) */
96
+ static const JSCellClass bigint_class = { "BigInt", NULL, NULL, NULL }; /* limbs only */
74
97
 
75
98
  static const JSCellClass *const cell_classes[JS_CELL_KIND_COUNT] = {
76
99
  [JS_CELL_STR_FLAT] = &js_flat_string_class,
@@ -98,19 +121,52 @@ static char *block_cell(JSBlock *b, uint32_t i) {
98
121
  return (char *)b + JS_BLOCK_FIRST_CELL + (size_t)i * b->cell_size;
99
122
  }
100
123
 
101
- static uint32_t cell_index(JSBlock *b, const void *c) {
102
- return (uint32_t)(((const char *)c - (const char *)b - JS_BLOCK_FIRST_CELL) / b->cell_size);
124
+ /* A multiply, not a divide: the marker computes this for every edge. Exact, since offsets are
125
+ * below 2^16 and sizes below 2^14 (offset * size < 2^32). A large object's magic is 0: its cell
126
+ * is the only one. */
127
+ static uint32_t cell_index(const JSBlock *b, const void *c) {
128
+ uint64_t offset = (uint64_t)((const char *)c - (const char *)b - JS_BLOCK_FIRST_CELL);
129
+ return (uint32_t)((offset * b->index_magic) >> 32);
103
130
  }
104
131
 
105
132
  static int bit_get(const uint64_t *bits, uint32_t i) { return (int)((bits[i / 64] >> (i % 64)) & 1); }
106
- static void bit_set(uint64_t *bits, uint32_t i) { bits[i / 64] |= UINT64_C(1) << (i % 64); }
107
- static void bit_clear(uint64_t *bits, uint32_t i) { bits[i / 64] &= ~(UINT64_C(1) << (i % 64)); }
108
133
 
109
- static void set_all_allocated(JSBlock *b) {
110
- uint32_t full = b->cell_count / 64, rest = b->cell_count % 64;
111
- memset(b->alloc_bits, 0xFF, full * sizeof(uint64_t));
112
- if (rest) b->alloc_bits[full] = (UINT64_C(1) << rest) - 1;
134
+ /* Without -mpopcnt (the default x86-64 target), __builtin_popcountll is a library call. */
135
+ static unsigned popcount64(uint64_t x) {
136
+ x -= (x >> 1) & UINT64_C(0x5555555555555555);
137
+ x = (x & UINT64_C(0x3333333333333333)) + ((x >> 2) & UINT64_C(0x3333333333333333));
138
+ x = (x + (x >> 4)) & UINT64_C(0x0F0F0F0F0F0F0F0F);
139
+ return (unsigned)((x * UINT64_C(0x0101010101010101)) >> 56);
140
+ }
141
+
142
+ /* Mark bits while several markers run: relaxed atomics. The
143
+ * packets that hand cells between markers go through a lock, which orders the rest. */
144
+ #if defined(_MSC_VER) && !defined(__clang__)
145
+ static inline uint64_t atomic_or64(uint64_t *p, uint64_t v) { return (uint64_t)_InterlockedOr64((volatile long long *)p, (long long)v); }
146
+ static inline uint64_t atomic_load64(const uint64_t *p) { return *(const volatile uint64_t *)p; }
147
+ static inline int atomic_load_int(const int *p) { return *(const volatile int *)p; }
148
+ static inline void atomic_store_int(int *p, int v) { *(volatile int *)p = v; }
149
+ #else
150
+ static inline uint64_t atomic_or64(uint64_t *p, uint64_t v) { return __atomic_fetch_or(p, v, __ATOMIC_RELAXED); }
151
+ static inline uint64_t atomic_load64(const uint64_t *p) { return __atomic_load_n(p, __ATOMIC_RELAXED); }
152
+ static inline int atomic_load_int(const int *p) { return __atomic_load_n(p, __ATOMIC_RELAXED); }
153
+ static inline void atomic_store_int(int *p, int v) { __atomic_store_n(p, v, __ATOMIC_RELAXED); }
154
+ #endif
155
+
156
+ #ifdef JS_GC_VERIFY
157
+ /* A dead cell is filled with POISON: allocation checks that nothing wrote it after it died, and
158
+ * the marker that nothing still points to it. */
159
+ #define POISON 0xDB
160
+
161
+ static void poison(void *c, uint32_t size) { memset(c, POISON, size); }
162
+
163
+ static void check_poison(const void *c, uint32_t size) {
164
+ const unsigned char *p = c;
165
+ for (uint32_t i = 0; i < size; i++)
166
+ if (p[i] != POISON)
167
+ js_fatal("GC verify: a dead cell (%u bytes at %p) was written at offset %u after it died", size, c, i);
113
168
  }
169
+ #endif
114
170
 
115
171
  /* ---- Block set: which 64 KiB chunks are small-object blocks ------------ */
116
172
 
@@ -180,6 +236,11 @@ static void block_set_remove(JSHeap *h, uintptr_t b) {
180
236
  /* The Bloom filter keeps the bit: it only ever answers "maybe". */
181
237
  }
182
238
 
239
+ static void note_heap_size(JSHeap *h) {
240
+ size_t size = (h->block_count - h->decommitted_count) * JS_BLOCK_SIZE + h->large_bytes;
241
+ if (size > h->stats.peak_heap_bytes) h->stats.peak_heap_bytes = size;
242
+ }
243
+
183
244
  static void note_mapping(JSHeap *h, const void *p, size_t size) {
184
245
  uintptr_t lo = (uintptr_t)p, hi = lo + size;
185
246
  if (!h->min_addr || lo < h->min_addr) h->min_addr = lo;
@@ -195,37 +256,51 @@ static JSBlock *map_block(JSHeap *h) {
195
256
  note_mapping(h, b, JS_BLOCK_SIZE);
196
257
  block_set_add(h, (uintptr_t)b);
197
258
  h->block_count++;
259
+ note_heap_size(h);
198
260
  return b;
199
261
  }
200
262
 
201
- /* Makes `b` the allocation block of its class with `list` (`n` cells) as
202
- * the free list. */
203
- static void *start_allocating(JSHeap *h, JSBlock *b, JSFreeCell *list, uint32_t n) {
204
- h->allocated_since_gc += (size_t)n * b->cell_size;
205
- set_all_allocated(b);
206
- h->current[b->size_class] = b;
207
- h->free[b->size_class] = list->next;
208
- return list;
209
- }
210
-
211
- /* Gives an empty block to size class `sc`; returns its first cell. */
212
- static void *block_init(JSHeap *h, JSBlock *b, unsigned sc) {
263
+ /* Gives an empty block to size class `sc`, every cell free. */
264
+ static void block_init(JSHeap *h, JSBlock *b, unsigned sc) {
213
265
  uint32_t size = js_size_class_bytes[sc];
214
266
  memset(b, 0, sizeof *b);
215
267
  b->map_size = JS_BLOCK_SIZE;
216
268
  b->cell_size = size;
217
269
  b->cell_count = (uint32_t)((JS_BLOCK_SIZE - JS_BLOCK_FIRST_CELL) / size);
218
270
  b->size_class = (uint8_t)sc;
219
-
220
- JSFreeCell *head = NULL;
221
- for (uint32_t i = b->cell_count; i-- > 0;) {
222
- JSFreeCell *c = (JSFreeCell *)block_cell(b, i);
223
- c->next = head;
224
- head = c;
271
+ b->index_magic = (uint32_t)(((UINT64_C(1) << 32) + size - 1) / size);
272
+ #ifdef JS_GC_VERIFY
273
+ for (uint32_t i = 0; i < b->cell_count; i++) {
274
+ char *c = block_cell(b, i);
275
+ poison(c, size);
276
+ if (cell_index(b, c) != i || cell_index(b, c + size - 1) != i)
277
+ js_fatal("GC verify: cell %u of a %u-byte block has index %u", i, size, cell_index(b, c));
225
278
  }
279
+ #endif
280
+ b->prev = NULL;
226
281
  b->next = h->blocks[sc];
282
+ if (b->next) b->next->prev = b;
227
283
  h->blocks[sc] = b;
228
- return start_allocating(h, b, head, b->cell_count);
284
+ }
285
+
286
+ static void unlink_block(JSHeap *h, JSBlock *b) {
287
+ if (b->prev) b->prev->next = b->next;
288
+ else h->blocks[b->size_class] = b->next;
289
+ if (b->next) b->next->prev = b->prev;
290
+ }
291
+
292
+ static void list_push(struct JSBlockList *l, JSBlock *b) {
293
+ if (l->count == l->cap) {
294
+ l->cap = l->cap ? l->cap * 2 : 64;
295
+ l->items = xrealloc(l->items, l->cap * sizeof *l->items);
296
+ }
297
+ l->items[l->count++] = b;
298
+ }
299
+
300
+ static void queue_sweep(JSHeap *h, JSBlock *b) {
301
+ if (b->queued) return;
302
+ b->queued = 1;
303
+ list_push(&h->to_sweep[b->size_class], b);
229
304
  }
230
305
 
231
306
  static void finalize_cell(JSRuntime *rt, JSCell *c) {
@@ -233,25 +308,22 @@ static void finalize_cell(JSRuntime *rt, JSCell *c) {
233
308
  if (cls->finalize) cls->finalize(rt, c);
234
309
  }
235
310
 
236
- /* Returns the dead cells of `b` as a free list (finalize_dead already ran their finalizers).
237
- * Afterwards only the survivors count as allocated. */
238
- static JSFreeCell *sweep_block(JSBlock *b, uint32_t *live_out) {
239
- JSFreeCell *head = NULL;
240
- uint32_t live = 0;
241
- for (uint32_t i = b->cell_count; i-- > 0;) {
242
- if (bit_get(b->mark_bits, i)) {
243
- live++;
244
- continue;
245
- }
246
- JSFreeCell *f = (JSFreeCell *)block_cell(b, i);
247
- f->next = head;
248
- head = f;
311
+ /* Sweeps `b`: its survivors (the marked cells) become its allocated cells, and the rest are free
312
+ * for the allocator. A word of the bitmap at a time, and no dead cell is written (checking builds
313
+ * poison the cells that died). finalize_dead already ran the dead cells' finalizers. Returns how
314
+ * many survived. */
315
+ static uint32_t sweep_block(JSBlock *b) {
316
+ uint32_t live = 0, words = (b->cell_count + 63) / 64;
317
+ for (uint32_t w = 0; w < words; w++) {
318
+ #ifdef JS_GC_VERIFY
319
+ for (uint64_t died = b->alloc_bits[w] & ~b->mark_bits[w]; died; died &= died - 1)
320
+ poison(block_cell(b, w * 64 + js_ctz64(died)), b->cell_size);
321
+ #endif
322
+ b->alloc_bits[w] = b->mark_bits[w];
323
+ live += popcount64(b->mark_bits[w]);
249
324
  }
250
- memcpy(b->alloc_bits, b->mark_bits, sizeof b->alloc_bits);
251
- b->unswept = 0;
252
325
  if (!live) b->has_finalizable = 0;
253
- *live_out = live;
254
- return head;
326
+ return live;
255
327
  }
256
328
 
257
329
  static void unmap_block(JSHeap *h, JSBlock *b) {
@@ -260,10 +332,50 @@ static void unmap_block(JSHeap *h, JSBlock *b) {
260
332
  h->block_count--;
261
333
  }
262
334
 
263
- /* Keeps a few empty blocks for reuse by any class; the rest go back to the OS. */
335
+ /* Gives an empty block's pages back to the OS. Its address stays mapped, for the allocator to
336
+ * reuse before it maps a new block; it's out of the block set, being no cells' block now. */
337
+ static void decommit_block(JSHeap *h, JSBlock *b) {
338
+ block_set_remove(h, (uintptr_t)b);
339
+ js_os_decommit(b, JS_BLOCK_SIZE);
340
+ if (h->decommitted_count == h->decommitted_cap) {
341
+ h->decommitted_cap = h->decommitted_cap ? h->decommitted_cap * 2 : 64;
342
+ h->decommitted = xrealloc(h->decommitted, h->decommitted_cap * sizeof *h->decommitted);
343
+ }
344
+ h->decommitted[h->decommitted_count++] = (struct JSDecommitted){ b, h->full_count };
345
+ }
346
+
347
+ /* A block for the allocator: a decommitted one (the most recent), or a new mapping. Zero-filled
348
+ * either way. */
349
+ static JSBlock *new_block(JSHeap *h) {
350
+ if (!h->decommitted_count) return map_block(h);
351
+ JSBlock *b = h->decommitted[--h->decommitted_count].block;
352
+ if (!js_os_commit(b, JS_BLOCK_SIZE)) js_fatal("out of memory committing a heap block");
353
+ block_set_add(h, (uintptr_t)b);
354
+ note_heap_size(h);
355
+ return b;
356
+ }
357
+
358
+ /* After a full collection: unmaps the decommitted blocks no allocation took since the one before. */
359
+ static void unmap_unused_blocks(JSHeap *h) {
360
+ size_t kept = 0;
361
+ for (size_t i = 0; i < h->decommitted_count; i++) {
362
+ struct JSDecommitted d = h->decommitted[i];
363
+ if (d.full_count + 1 < h->full_count) {
364
+ js_os_unmap(d.block, JS_BLOCK_SIZE);
365
+ h->block_count--;
366
+ } else {
367
+ h->decommitted[kept++] = d;
368
+ }
369
+ }
370
+ h->decommitted_count = kept;
371
+ }
372
+
373
+ /* Keeps empty blocks for reuse by any class, as many as the program allocates before the next
374
+ * collection (the nursery): the heap grows that far before then anyway, and a block given back now
375
+ * would have its pages faulted in again one by one. The rest go back to the OS. */
264
376
  static void release_empty_block(JSHeap *h, JSBlock *b) {
265
- if (h->empty_count >= JS_EMPTY_POOL_MAX) {
266
- unmap_block(h, b);
377
+ if (h->empty_count >= h->nursery / JS_BLOCK_SIZE) {
378
+ decommit_block(h, b);
267
379
  return;
268
380
  }
269
381
  b->next = h->empty;
@@ -271,36 +383,30 @@ static void release_empty_block(JSHeap *h, JSBlock *b) {
271
383
  h->empty_count++;
272
384
  }
273
385
 
274
- /* Sweeps the next unswept block of class `sc`. Returns 0 when there is none;
275
- * otherwise stores its free list (NULL if it had no dead cells) in *list and
276
- * the list's length in *n. */
277
- static int sweep_next(JSRuntime *rt, unsigned sc, JSFreeCell **list, uint32_t *n) {
386
+ /* Sweeps the next queued block of class `sc`. Returns false when there is none; otherwise sets
387
+ * *out to the block if it has free cells, or to NULL: it's full, or empty and in the pool. An empty
388
+ * block stays in its class when the class is allocating (`allocating`). */
389
+ static bool sweep_next(JSRuntime *rt, unsigned sc, bool allocating, JSBlock **out) {
278
390
  JSHeap *h = &rt->heap;
279
- JSBlock **link = h->sweep_link[sc];
280
- while (*link && !(*link)->unswept) link = &(*link)->next;
281
- JSBlock *b = *link;
282
- if (!b) {
283
- h->sweep_link[sc] = link;
284
- return 0;
285
- }
286
- uint32_t live;
287
- JSFreeCell *free_list = sweep_block(b, &live);
288
- *n = b->cell_count - live; /* before b may be unmapped below */
289
- if (!live) {
290
- *link = b->next; /* unlink; an empty block can serve any class */
391
+ struct JSBlockList *q = &h->to_sweep[sc];
392
+ if (!q->count) return false;
393
+ JSBlock *b = q->items[--q->count];
394
+ b->queued = 0;
395
+ double t0 = h->report ? js_os_now_ms_precise() : 0;
396
+ uint32_t live = sweep_block(b);
397
+ if (h->report) h->stats.sweep_ms += js_os_now_ms_precise() - t0;
398
+ h->stats.blocks_swept++;
399
+ *out = live < b->cell_count ? b : NULL;
400
+ if (!live && !allocating) {
401
+ unlink_block(h, b); /* an empty block can serve any class */
291
402
  release_empty_block(h, b);
292
- free_list = NULL;
293
- } else {
294
- link = &b->next;
403
+ *out = NULL;
295
404
  }
296
- h->sweep_link[sc] = link;
297
- *list = free_list;
298
- return 1;
405
+ return true;
299
406
  }
300
407
 
301
408
  static int should_collect(const JSHeap *h) {
302
- size_t threshold = h->live_after_gc > h->min_heap ? h->live_after_gc : h->min_heap;
303
- return h->allocated_since_gc > threshold;
409
+ return h->allocated_since_gc + h->external_since_gc > h->nursery;
304
410
  }
305
411
 
306
412
  static int over_limit(const JSRuntime *rt, size_t more) {
@@ -311,59 +417,203 @@ static _Noreturn void heap_limit_reached(const JSHeap *h) {
311
417
  js_fatal("heap limit reached (%zu MiB)", h->heap_limit >> 20);
312
418
  }
313
419
 
420
+ static void collect_as_needed(JSContext *ctx, bool near_limit);
421
+ static void collect_spilled(JSContext *ctx, bool full);
422
+ static void mark_slice(JSContext *ctx, size_t budget);
423
+ static void start_marking(JSContext *ctx);
424
+ static size_t allocation_clock(const JSHeap *h);
425
+ static void gc_poll(JSContext *ctx);
426
+ static void finish_sweeping(JSRuntime *rt);
427
+
314
428
  #ifdef JS_GC_STRESS
315
429
  /* Collects on every allocation (every JS_GC_STRESS_INTERVAL-th). */
316
430
  static void stress_tick(JSContext *ctx) {
317
431
  JSHeap *h = &ctx->rt->heap;
318
432
  if (!h->in_gc && ++h->stress_counter >= h->stress_interval) {
319
433
  h->stress_counter = 0;
320
- js_gc(ctx);
434
+ if (h->marker) { /* a small slice: the marking spans many allocations, and their stores */
435
+ mark_slice(ctx, 4096);
436
+ return;
437
+ }
438
+ switch (++h->stress_collections % 8) { /* every 4th is full: stopping the world, or incremental */
439
+ case 0: js_gc(ctx); break;
440
+ case 4: if (h->incremental) start_marking(ctx); else js_gc(ctx); break;
441
+ default: js_gc_young(ctx); break;
442
+ }
321
443
  }
322
444
  }
323
445
  #endif
324
446
 
325
- /* Lazily sweeps blocks of class `sc` until one has a dead cell to reuse. */
326
- static void *sweep_for(JSRuntime *rt, unsigned sc) {
327
- JSFreeCell *list;
328
- uint32_t n;
329
- while (sweep_next(rt, sc, &list, &n)) {
330
- if (list) return start_allocating(&rt->heap, js_block_of(list), list, n);
331
- }
447
+ /* ---- Allocation: a word of the bitmap at a time ---------------------- */
448
+
449
+ /* Lazily sweeps blocks of class `sc` until one has free cells; NULL if none does. */
450
+ static JSBlock *sweep_for(JSRuntime *rt, unsigned sc) {
451
+ JSBlock *b;
452
+ while (sweep_next(rt, sc, true, &b))
453
+ if (b) return b;
332
454
  return NULL;
333
455
  }
334
456
 
335
- void *js_alloc_slow(JSContext *ctx, unsigned sc) {
336
- JSRuntime *rt = ctx->rt;
337
- JSHeap *h = &rt->heap;
338
- #ifdef JS_GC_STRESS
339
- stress_tick(ctx);
340
- JSFreeCell *c = h->free[sc];
341
- if (c) {
342
- h->free[sc] = c->next;
343
- return c;
457
+ static void use_block(JSHeap *h, JSAllocator *a, JSBlock *b, bool fresh) {
458
+ if (!b->young) {
459
+ b->young = 1;
460
+ list_push(&h->young_blocks, b);
344
461
  }
462
+ a->block = b;
463
+ a->word = 0;
464
+ a->fresh = fresh;
465
+ }
466
+
467
+ #ifndef JS_GC_VERIFY
468
+ /* Zeroes the window's free cells, a run of them at a time. */
469
+ static void zero_window(const JSAllocator *a) {
470
+ for (uint64_t m = a->free; m;) {
471
+ unsigned start = js_ctz64(m);
472
+ uint64_t rest = ~(m >> start); /* its lowest set bit ends the run */
473
+ unsigned n = rest ? js_ctz64(rest) : 64 - start;
474
+ memset((void *)(a->base + start * a->cell_size), 0, n * a->cell_size);
475
+ if (start + n >= 64) break;
476
+ m &= ~UINT64_C(0) << (start + n);
477
+ }
478
+ }
345
479
  #endif
346
- for (int collected = 0;; collected = 1) {
347
- void *cell = sweep_for(rt, sc);
348
- if (cell) return cell;
349
- JSBlock *b = h->empty;
480
+
481
+ /* Takes the block's next word with free cells, after a->word. Returns false when it has none. */
482
+ static bool next_window(JSHeap *h, JSAllocator *a) {
483
+ JSBlock *b = a->block;
484
+ uint32_t words = (b->cell_count + 63) / 64;
485
+ for (uint32_t w = a->word; w < words; w++) {
486
+ uint64_t free = ~b->alloc_bits[w];
487
+ if (w == words - 1 && b->cell_count % 64) free &= (UINT64_C(1) << (b->cell_count % 64)) - 1;
488
+ if (!free) continue;
489
+ b->alloc_bits[w] |= free; /* any of them may be handed out */
490
+ a->free = free;
491
+ a->base = (uintptr_t)block_cell(b, w * 64);
492
+ a->word = w + 1;
493
+ h->allocated_since_gc += (size_t)popcount64(free) * b->cell_size;
494
+ #ifndef JS_GC_VERIFY
495
+ if (!a->fresh) zero_window(a); /* checking builds zero a cell when it's handed out */
496
+ #endif
497
+ return true;
498
+ }
499
+ return false;
500
+ }
501
+
502
+ /* Gives class `sc` a window of free cells when its block has none left: the next swept block with
503
+ * free cells, an empty block, a collection, or a new block, in that order. A young collection
504
+ * comes when the heap would grow, not at every block: after a full collection the blocks' free
505
+ * room serves as a larger nursery (checking the nursery at every block was measured: binary-trees
506
+ * promoted its trees half-built, and collected 2.4 times as long). Not inlined: the slow path's
507
+ * common case is the block's next word, and stays small. */
508
+ static JS_NOINLINE void refill(JSContext *ctx, unsigned sc) {
509
+ JSRuntime *rt = ctx->rt;
510
+ JSHeap *h = &rt->heap;
511
+ JSAllocator *a = &h->alloc[sc];
512
+ bool collected = false, collected_full = false; /* the heap limit's error comes after a full one */
513
+ for (;;) {
514
+ JSBlock *b = sweep_for(rt, sc);
515
+ if (!b && h->ready[sc].count) b = h->ready[sc].items[--h->ready[sc].count];
350
516
  if (b) {
517
+ use_block(h, a, b, false);
518
+ if (next_window(h, a)) return;
519
+ continue; /* can't happen: sweep_for's blocks have free cells */
520
+ }
521
+ if ((b = h->empty)) {
351
522
  h->empty = b->next;
352
523
  h->empty_count--;
353
- return block_init(h, b, sc);
524
+ block_init(h, b, sc);
525
+ use_block(h, a, b, false);
526
+ next_window(h, a);
527
+ return;
354
528
  }
355
- if (!collected && !h->in_gc && (should_collect(h) || over_limit(rt, JS_BLOCK_SIZE))) {
356
- js_gc(ctx);
529
+ bool near_limit = over_limit(rt, JS_BLOCK_SIZE);
530
+ bool young_due = !collected && !h->marker && should_collect(h); /* a marking has slices instead */
531
+ if (!h->in_gc && (young_due || (near_limit && !collected_full))) {
532
+ collected = true;
533
+ collected_full |= near_limit || h->full_next;
534
+ collect_as_needed(ctx, near_limit); /* stops allocation: `a` has no block now */
357
535
  continue;
358
536
  }
359
537
  if (over_limit(rt, JS_BLOCK_SIZE)) heap_limit_reached(h);
360
- return block_init(h, map_block(h), sc);
538
+ b = new_block(h);
539
+ block_init(h, b, sc);
540
+ #ifdef JS_GC_VERIFY
541
+ use_block(h, a, b, false); /* poisoned */
542
+ #else
543
+ use_block(h, a, b, true);
544
+ #endif
545
+ next_window(h, a);
546
+ return;
361
547
  }
362
548
  }
363
549
 
550
+ /* The window is used up: take the block's next word, or refill. Checking builds take every
551
+ * allocation here (js_alloc_small). */
552
+ void *js_alloc_slow(JSContext *ctx, unsigned sc) {
553
+ #ifdef JS_GC_STRESS
554
+ stress_tick(ctx);
555
+ #endif
556
+ JSHeap *h = &ctx->rt->heap;
557
+ if (JS_UNLIKELY(h->collect_requested || h->marker) && !h->in_gc) gc_poll(ctx);
558
+ JSAllocator *a = &h->alloc[sc];
559
+ if (!a->free && !(a->block && next_window(h, a))) refill(ctx, sc);
560
+ uint64_t free = a->free;
561
+ a->free = free & (free - 1);
562
+ void *c = (void *)(a->base + js_ctz64(free) * a->cell_size);
563
+ #ifdef JS_GC_VERIFY
564
+ check_poison(c, (uint32_t)a->cell_size);
565
+ memset(c, 0, a->cell_size);
566
+ #endif
567
+ return c;
568
+ }
569
+
364
570
  /* ---- Large objects ----------------------------------------------------- */
365
571
 
366
- /* Returns empty blocks to the OS to make room under the heap limit. */
572
+ /* Dead large objects' mappings, kept for the next large objects as the empty-block pool is for
573
+ * blocks, up to a nursery's worth: a large object mapped anew is a system call to map it, one to
574
+ * unmap it, and a page fault per page it uses. Counted in large_bytes (malloc'd). */
575
+ static void unmap_large_cache(JSHeap *h) {
576
+ for (size_t i = 0; i < h->large_cache_count; i++) {
577
+ h->large_bytes -= h->large_cache[i]->map_size;
578
+ js_os_unmap(h->large_cache[i], h->large_cache[i]->map_size);
579
+ }
580
+ h->large_cache_count = 0;
581
+ h->large_cache_bytes = 0;
582
+ }
583
+
584
+ static void cache_large(JSHeap *h, JSBlock *b) {
585
+ if (h->large_cache_bytes + b->map_size > h->nursery) {
586
+ h->large_bytes -= b->map_size;
587
+ js_os_unmap(b, b->map_size);
588
+ return;
589
+ }
590
+ if (h->large_cache_count == h->large_cache_cap) {
591
+ h->large_cache_cap = h->large_cache_cap ? h->large_cache_cap * 2 : 16;
592
+ h->large_cache = xrealloc(h->large_cache, h->large_cache_cap * sizeof *h->large_cache);
593
+ }
594
+ h->large_cache[h->large_cache_count++] = b;
595
+ h->large_cache_bytes += b->map_size;
596
+ }
597
+
598
+ /* The smallest cached mapping of at least `map_size` bytes that wastes at most a quarter, zeroed
599
+ * as far as the cell goes; NULL if none. */
600
+ static JSBlock *reuse_large(JSHeap *h, size_t map_size, size_t size) {
601
+ size_t best = SIZE_MAX;
602
+ for (size_t i = 0; i < h->large_cache_count; i++) {
603
+ size_t m = h->large_cache[i]->map_size;
604
+ if (m >= map_size && m <= map_size + map_size / 4 && (best == SIZE_MAX || m < h->large_cache[best]->map_size)) best = i;
605
+ }
606
+ if (best == SIZE_MAX) return NULL;
607
+ JSBlock *b = h->large_cache[best];
608
+ h->large_cache[best] = h->large_cache[--h->large_cache_count];
609
+ h->large_cache_bytes -= b->map_size;
610
+ size_t mapped = b->map_size;
611
+ memset(b, 0, JS_BLOCK_FIRST_CELL + size);
612
+ b->map_size = mapped;
613
+ return b;
614
+ }
615
+
616
+ /* Returns empty blocks and cached mappings to the OS to make room under the heap limit. */
367
617
  static void drain_empty_pool(JSHeap *h) {
368
618
  while (h->empty) {
369
619
  JSBlock *b = h->empty;
@@ -371,6 +621,7 @@ static void drain_empty_pool(JSHeap *h) {
371
621
  h->empty_count--;
372
622
  unmap_block(h, b);
373
623
  }
624
+ unmap_large_cache(h);
374
625
  }
375
626
 
376
627
  static JSCell *alloc_large(JSContext *ctx, size_t size) {
@@ -381,18 +632,33 @@ static JSCell *alloc_large(JSContext *ctx, size_t size) {
381
632
  #ifdef JS_GC_STRESS
382
633
  stress_tick(ctx);
383
634
  #endif
384
- if (!h->in_gc && (should_collect(h) || over_limit(rt, map_size))) js_gc(ctx); /* large objects are swept eagerly */
385
- if (over_limit(rt, map_size)) drain_empty_pool(h);
386
- if (over_limit(rt, map_size)) heap_limit_reached(h);
635
+ if (!h->in_gc) {
636
+ if (over_limit(rt, map_size) || (!h->marker && should_collect(h))) collect_as_needed(ctx, over_limit(rt, map_size));
637
+ else if (h->marker) gc_poll(ctx);
638
+ }
639
+ JSBlock *b = reuse_large(h, map_size, size);
640
+ if (b) {
641
+ map_size = b->map_size;
642
+ } else {
643
+ if (over_limit(rt, map_size)) { /* what the collection freed, out of the lazy sweeping's queues */
644
+ finish_sweeping(rt);
645
+ drain_empty_pool(h);
646
+ }
647
+ if (over_limit(rt, map_size)) heap_limit_reached(h);
648
+ b = js_os_map_aligned(map_size, JS_BLOCK_SIZE); /* zero-filled */
649
+ if (!b) js_fatal("out of memory allocating %zu bytes", size);
650
+ check_address(b, map_size);
651
+ note_mapping(h, b, map_size);
652
+ b->map_size = map_size;
653
+ h->large_bytes += map_size;
654
+ }
387
655
  h->allocated_since_gc += map_size;
388
- JSBlock *b = js_os_map_aligned(map_size, JS_BLOCK_SIZE); /* zero-filled */
389
- if (!b) js_fatal("out of memory allocating %zu bytes", size);
390
- check_address(b, map_size);
391
- note_mapping(h, b, map_size);
392
- b->map_size = map_size;
393
656
  b->cell_size = (uint32_t)(size <= UINT32_MAX ? size : UINT32_MAX);
394
657
  b->cell_count = 1;
395
658
  b->is_large = 1;
659
+ uint8_t shift = 9; /* cards of 512 bytes, or more for a very large object */
660
+ while ((size + ((size_t)1 << shift) - 1) >> shift > JS_LARGE_CARDS) shift++;
661
+ b->size_class = shift;
396
662
  b->alloc_bits[0] = 1;
397
663
 
398
664
  if (h->large_count == h->large_cap) {
@@ -406,7 +672,7 @@ static JSCell *alloc_large(JSContext *ctx, size_t size) {
406
672
  }
407
673
  h->large[i] = b;
408
674
  h->large_count++;
409
- h->large_bytes += map_size;
675
+ note_heap_size(h);
410
676
  return (JSCell *)block_cell(b, 0);
411
677
  }
412
678
 
@@ -419,8 +685,7 @@ static void sweep_large(JSRuntime *rt) {
419
685
  h->large[kept++] = b;
420
686
  continue;
421
687
  }
422
- h->large_bytes -= b->map_size;
423
- js_os_unmap(b, b->map_size);
688
+ cache_large(h, b);
424
689
  }
425
690
  h->large_count = kept;
426
691
  }
@@ -452,6 +717,51 @@ JSHostCell *js_alloc_host_cell(JSContext *ctx, const JSCellClass *cls, size_t si
452
717
  return h;
453
718
  }
454
719
 
720
+ /* ---- External memory --------------------------------------------------- */
721
+
722
+ static void stop_allocation(JSHeap *h);
723
+
724
+ /* Counted as allocated since the last collection, as cells are. A collection can't run here (the
725
+ * caller may be between allocating its bytes and the cell that owns them): stopping allocation
726
+ * sends the next allocation, in any size class, to the slow path, which collects. */
727
+ void js_gc_external_add(JSRuntime *rt, size_t bytes) {
728
+ JSHeap *h = &rt->heap;
729
+ h->external_bytes += bytes;
730
+ h->external_since_gc += bytes;
731
+ if (h->external_bytes > h->stats.peak_external_bytes) h->stats.peak_external_bytes = h->external_bytes;
732
+ if (!h->in_gc && !h->collect_requested && (h->marker ? allocation_clock(h) >= h->next_slice : should_collect(h))) {
733
+ h->collect_requested = 1;
734
+ stop_allocation(h);
735
+ }
736
+ }
737
+
738
+ void js_gc_external_sub(JSRuntime *rt, size_t bytes) {
739
+ JSHeap *h = &rt->heap;
740
+ #ifdef JS_GC_VERIFY
741
+ if (bytes > h->external_bytes) js_fatal("GC verify: %zu external bytes freed, but only %zu counted", bytes, h->external_bytes);
742
+ #endif
743
+ h->external_bytes -= bytes < h->external_bytes ? bytes : h->external_bytes;
744
+ }
745
+
746
+ /* The size goes in a header before the bytes, a max_align_t's worth so they stay aligned. */
747
+ #define EXTERNAL_HEADER sizeof(max_align_t)
748
+
749
+ void *js_gc_external_malloc(JSRuntime *rt, size_t bytes) {
750
+ if (bytes > SIZE_MAX - EXTERNAL_HEADER) return NULL;
751
+ char *p = malloc(EXTERNAL_HEADER + bytes);
752
+ if (!p) return NULL;
753
+ *(size_t *)p = bytes;
754
+ js_gc_external_add(rt, bytes);
755
+ return p + EXTERNAL_HEADER;
756
+ }
757
+
758
+ void js_gc_external_free(JSRuntime *rt, void *p) {
759
+ if (!p) return;
760
+ char *start = (char *)p - EXTERNAL_HEADER;
761
+ js_gc_external_sub(rt, *(size_t *)start);
762
+ free(start);
763
+ }
764
+
455
765
  /* ---- Roots ------------------------------------------------------------- */
456
766
 
457
767
  void js_register_roots(JSRuntime *rt, JSValue *slots, size_t n) {
@@ -482,47 +792,859 @@ void js_remove_root(JSRuntime *rt, JSValue *slot) {
482
792
  }
483
793
  }
484
794
 
795
+ /* ---- Generations: the write barrier's remembered set ------------------- */
796
+
797
+ static bool is_object_kind(const JSCell *c) {
798
+ return c->kind == JS_CELL_OBJECT || c->kind == JS_CELL_ARRAY || c->kind == JS_CELL_FUNCTION;
799
+ }
800
+
801
+ static void push_remembered(JSHeap *h, JSCell *c) {
802
+ if (h->remembered_count == h->remembered_cap) {
803
+ h->remembered_cap = h->remembered_cap ? h->remembered_cap * 2 : 1024;
804
+ h->remembered = xrealloc(h->remembered, h->remembered_cap * sizeof *h->remembered);
805
+ }
806
+ h->remembered[h->remembered_count++] = c;
807
+ }
808
+
809
+ /* Marks the cards of large `c` that bytes [from, from + bytes) are in; all of them when `from` is
810
+ * NULL or outside the cell (malloc'd memory its visit traces). It stays JS_GC_OLD_LARGE, so its
811
+ * next stores of young cells mark their own cards. */
812
+ static void mark_cards(JSHeap *h, JSCell *c, const void *from, size_t bytes) {
813
+ JSBlock *b = js_block_of(c);
814
+ unsigned shift = b->size_class;
815
+ size_t size = b->cell_size, off = (size_t)((const char *)from - (const char *)c);
816
+ if (!from || (const char *)from < (const char *)c || off >= size) off = 0, bytes = size;
817
+ if (!bytes) bytes = 1;
818
+ size_t last = (off + bytes - 1 < size ? off + bytes - 1 : size - 1) >> shift;
819
+ uint64_t *cards = js_large_cards(b);
820
+ for (size_t k = off >> shift; k <= last; k++) cards[k / 64] |= UINT64_C(1) << (k % 64);
821
+ if (!b->remembered) {
822
+ b->remembered = 1;
823
+ push_remembered(h, c);
824
+ }
825
+ }
826
+
827
+ static void remember_one(JSHeap *h, JSCell *c) {
828
+ if (c->gc == JS_GC_OLD_LARGE) {
829
+ mark_cards(h, c, NULL, 0);
830
+ return;
831
+ }
832
+ c->gc = JS_GC_REMEMBERED; /* below the barrier's threshold: its next stores are fast */
833
+ push_remembered(h, c);
834
+ }
835
+
836
+ /* An object's out-of-line slots are stored with the object as the owner (js_object_slot), so
837
+ * remembering an object remembers them too, while they're old: then the next stores, which skip
838
+ * the barrier, are covered. A new slot array is young. Remembering the array itself, not the
839
+ * object, keeps it exact: an array the object drops still keeps what it was given until the next
840
+ * young collection, as every remembered cell does. */
841
+ static JS_NOINLINE void remember(JSHeap *h, JSCell *c) {
842
+ remember_one(h, c);
843
+ if (is_object_kind(c)) {
844
+ JSValueArray *ool = ((JSObject *)c)->ool;
845
+ if (ool && ool->cell.gc >= JS_GC_OLD) remember_one(h, &ool->cell);
846
+ }
847
+ }
848
+
849
+ static void shade(JSTracer *trc, JSCell *c);
850
+
851
+ /* During an incremental marking the barrier marks what's stored
852
+ * (Dijkstra's): a cell the marker has visited never gets an unmarked one behind its back. The
853
+ * slow path runs for every owner at or above JS_GC_OLD, which every marked cell is (mark sets
854
+ * it), and old cells the marker hasn't reached yet: marking what they're given only keeps it
855
+ * one collection longer. There are no young collections during a marking, so nothing is
856
+ * remembered. */
857
+ void js_write_barrier_slow(JSContext *ctx, JSCell *owner, const void *slot, const JSCell *c) {
858
+ JSHeap *h = &ctx->rt->heap;
859
+ if (JS_UNLIKELY(h->marker)) {
860
+ shade(h->marker, (JSCell *)c);
861
+ return;
862
+ }
863
+ if (c->gc != JS_GC_YOUNG || js_cell_is_static(c)) return;
864
+ if (owner->gc == JS_GC_OLD) remember(h, owner);
865
+ else if (owner->gc == JS_GC_OLD_LARGE) mark_cards(h, owner, slot, sizeof(JSValue));
866
+ }
867
+
868
+ /* js_store_range's: `from` is `bytes` of JSValues. */
869
+ void js_remember(JSContext *ctx, JSCell *owner, const void *from, size_t bytes) {
870
+ JSHeap *h = &ctx->rt->heap;
871
+ if (JS_UNLIKELY(h->marker)) {
872
+ const JSValue *v = from;
873
+ for (size_t i = 0; i < bytes / sizeof *v; i++)
874
+ if (js_is_cell(v[i])) shade(h->marker, js_as_cell(v[i]));
875
+ return;
876
+ }
877
+ if (owner->gc == JS_GC_OLD) remember(h, owner);
878
+ else if (owner->gc == JS_GC_OLD_LARGE) mark_cards(h, owner, from, bytes);
879
+ }
880
+
485
881
  /* ---- Marking ----------------------------------------------------------- */
486
882
 
487
- void js_trace_cell(JSTracer *trc, JSCell *c) {
883
+ #ifdef JS_GC_VERIFY
884
+ static JSCell *find_cell(const JSHeap *h, uintptr_t w);
885
+ static JSBlock *find_large(const JSHeap *h, uintptr_t w);
886
+
887
+ static const char *kind_name(const JSCell *c) {
888
+ if (!c) return "a root";
889
+ if (c->kind == JS_CELL_HOST) return ((const JSHostCell *)c)->cls->name;
890
+ return c->kind > JS_CELL_INVALID && c->kind < JS_CELL_KIND_COUNT ? cell_classes[c->kind]->name : "a cell of no kind";
891
+ }
892
+
893
+ static bool in_heap(const JSHeap *h, uintptr_t w) {
894
+ return w >= h->min_addr && w < h->max_addr &&
895
+ (block_set_contains(h, w & ~(uintptr_t)(JS_BLOCK_SIZE - 1)) || find_large(h, w));
896
+ }
897
+
898
+ /* Every pointer the marker follows must be a static cell outside the heap, or the start of a cell
899
+ * in it that's allocated and has a kind. Which one is decided by the address: a dead cell's header
900
+ * is stale (or poison), and its bits can read as the static flag. Returns whether `c` is static. */
901
+ static bool verify_edge(const JSTracer *trc, const JSCell *c) {
902
+ const char *what = NULL;
903
+ if (!in_heap(trc->heap, (uintptr_t)c)) {
904
+ if (js_cell_is_static(c)) return true;
905
+ what = "not in the heap, and not a static cell";
906
+ } else {
907
+ JSCell *found = find_cell(trc->heap, (uintptr_t)c);
908
+ if (!found) what = "not an allocated cell (dead, or not in the heap)";
909
+ else if (found != c) what = "inside a cell, not at its start";
910
+ else if (c->kind == JS_CELL_INVALID || c->kind >= JS_CELL_KIND_COUNT) what = "a cell with no valid kind";
911
+ else if (js_cell_is_static(c)) what = "a heap cell flagged static";
912
+ }
913
+ if (what) js_fatal("GC verify: %s %p points to %p, which is %s", kind_name(trc->visiting), (const void *)trc->visiting, (const void *)c, what);
914
+ return false;
915
+ }
916
+ #endif
917
+
918
+ /* Kinds that hold no cells: marked, never pushed or visited. */
919
+ static const bool leaf_kind[JS_CELL_KIND_COUNT] = { [JS_CELL_STR_FLAT] = true, [JS_CELL_BIGINT] = true };
920
+
921
+ static JS_NOINLINE void grow_stack(JSTracer *trc) {
922
+ trc->cap = trc->cap ? trc->cap * 2 : 4096;
923
+ trc->stack = xrealloc(trc->stack, trc->cap * sizeof *trc->stack);
924
+ }
925
+
926
+ /* Sets a mark bit; returns whether it was clear: this marker marked the cell, and visits it. */
927
+ static inline bool set_mark(JSTracer *trc, uint64_t *word, uint64_t bit) {
928
+ if (trc->parallel) {
929
+ if (atomic_load64(word) & bit) return false;
930
+ return !(atomic_or64(word, bit) & bit);
931
+ }
932
+ if (*word & bit) return false;
933
+ *word |= bit;
934
+ return true;
935
+ }
936
+
937
+ static inline void push(JSTracer *trc, JSCell *c) {
938
+ if (trc->sp == trc->cap) grow_stack(trc);
939
+ trc->stack[trc->sp++] = c;
940
+ }
941
+
942
+ /* Marks `c`, and pushes it if it holds cells. Its header is read here, the first touch of the
943
+ * cell: trace_cell prefetched it. */
944
+ static inline void mark(JSTracer *trc, JSCell *c) {
945
+ #ifdef JS_GC_VERIFY
946
+ if (verify_edge(trc, c)) return;
947
+ #else
488
948
  if (js_cell_is_static(c)) return;
949
+ #endif
489
950
  JSBlock *b = js_block_of(c);
490
951
  uint32_t i = cell_index(b, c);
491
- if (bit_get(b->mark_bits, i)) return;
492
- bit_set(b->mark_bits, i);
493
- JSHeap *h = trc->heap;
494
- if (trc->sp == h->mark_stack_cap) {
495
- h->mark_stack_cap = h->mark_stack_cap ? h->mark_stack_cap * 2 : 4096;
496
- h->mark_stack = xrealloc(h->mark_stack, h->mark_stack_cap * sizeof *h->mark_stack);
952
+ if (!set_mark(trc, &b->mark_bits[i / 64], UINT64_C(1) << (i % 64))) return; /* in a young collection, every old cell is */
953
+ c->gc = b->is_large ? JS_GC_OLD_LARGE : JS_GC_OLD; /* it survives: old from now on */
954
+ trc->marked++;
955
+ trc->marked_bytes += b->is_large ? b->map_size : b->cell_size;
956
+ if (leaf_kind[c->kind] || (c->kind == JS_CELL_HOST && !((JSHostCell *)c)->cls->visit)) return;
957
+ push(trc, c);
958
+ }
959
+
960
+ /* The marking barrier's: marked now, and on the marker's stack to be visited. */
961
+ static void shade(JSTracer *trc, JSCell *c) { mark(trc, c); }
962
+
963
+ /* An edge: its cell is prefetched and marked MARK_FIFO edges later, when the cache line has
964
+ * arrived (most of the marker's time was that miss). A checking build marks at once, so a bad
965
+ * edge is reported with the cell it came from. */
966
+ #ifdef JS_GC_VERIFY
967
+ static JS_NOINLINE void check_old_edge(JSTracer *trc, JSCell *c) {
968
+ if (verify_edge(trc, c) || js_gc_is_marked(c)) return;
969
+ if (trc->check_black)
970
+ js_fatal("GC verify: marked %s %p points to unmarked %s %p after the marking: a store without js_store?",
971
+ kind_name(trc->visiting), (const void *)trc->visiting, kind_name(c), (const void *)c);
972
+ js_fatal("GC verify: old %s %p points to young %s %p but isn't remembered: a store without js_store?",
973
+ kind_name(trc->visiting), (const void *)trc->visiting, kind_name(c), (const void *)c);
974
+ }
975
+ #endif
976
+
977
+ static inline void trace_cell(JSTracer *trc, JSCell *c) {
978
+ #ifdef JS_GC_VERIFY
979
+ if (trc->check_old) check_old_edge(trc, c);
980
+ else mark(trc, c);
981
+ #else
982
+ #if defined(__clang__) || defined(__GNUC__)
983
+ __builtin_prefetch(c);
984
+ #endif
985
+ if (trc->fifo_count < MARK_FIFO) {
986
+ trc->fifo[(trc->fifo_head + trc->fifo_count++) % MARK_FIFO] = c;
987
+ return;
497
988
  }
498
- h->mark_stack[trc->sp++] = c;
989
+ JSCell *oldest = trc->fifo[trc->fifo_head];
990
+ trc->fifo[trc->fifo_head] = c;
991
+ trc->fifo_head = (trc->fifo_head + 1) % MARK_FIFO;
992
+ mark(trc, oldest);
993
+ #endif
994
+ }
995
+
996
+ static inline void trace_value(JSTracer *trc, JSValue v) {
997
+ if (js_is_cell(v)) trace_cell(trc, js_as_cell(v));
499
998
  }
500
999
 
1000
+ /* An object's out-of-line slots are traced with the object, by its visit: marked, never pushed.
1001
+ * Stores into them have the object as their owner (js_object_slot), so it's the object the
1002
+ * barrier sees; during an incremental marking a stack word (an interior pointer to a slot) can
1003
+ * have marked the slots first, and when the object is young those stores skip the barrier. The
1004
+ * object's visit traces them anyway. */
1005
+ static inline void mark_ool(JSTracer *trc, JSValueArray *a) {
1006
+ JSCell *c = &a->cell;
1007
+ #ifdef JS_GC_VERIFY
1008
+ if (trc->check_old) { /* the verifiers: the slots are an old cell of their own, checked as one */
1009
+ check_old_edge(trc, c);
1010
+ return;
1011
+ }
1012
+ if (verify_edge(trc, c)) return;
1013
+ #endif
1014
+ JSBlock *b = js_block_of(c);
1015
+ uint32_t i = cell_index(b, c);
1016
+ if (set_mark(trc, &b->mark_bits[i / 64], UINT64_C(1) << (i % 64))) {
1017
+ c->gc = b->is_large ? JS_GC_OLD_LARGE : JS_GC_OLD;
1018
+ trc->marked++;
1019
+ trc->marked_bytes += b->is_large ? b->map_size : b->cell_size;
1020
+ }
1021
+ for (uint32_t k = 0; k < c->aux; k++) trace_value(trc, a->values[k]);
1022
+ }
1023
+
1024
+ #include "gc_visit.h"
1025
+
1026
+ void js_trace_cell(JSTracer *trc, JSCell *c) { trace_cell(trc, c); }
1027
+ void js_trace_value(JSTracer *trc, JSValue v) { trace_value(trc, v); }
1028
+
501
1029
  bool js_gc_is_marked(const JSCell *c) {
502
1030
  if (js_cell_is_static(c)) return true;
503
1031
  JSBlock *b = js_block_of(c);
504
1032
  return bit_get(b->mark_bits, cell_index(b, c));
505
1033
  }
506
1034
 
507
- void js_trace_value(JSTracer *trc, JSValue v) {
508
- if (js_is_cell(v)) js_trace_cell(trc, js_as_cell(v));
1035
+ /* Inlined into drain, the marker's loop: the rest of its callers are rare. */
1036
+ static JS_ALWAYS_INLINE void visit(JSTracer *trc, JSCell *c) {
1037
+ #ifdef JS_GC_VERIFY
1038
+ trc->visiting = c;
1039
+ #endif
1040
+ switch (c->kind) {
1041
+ case JS_CELL_OBJECT: case JS_CELL_ARRAY: case JS_CELL_FUNCTION: visit_object(c, trc); break;
1042
+ case JS_CELL_ENV: visit_env(c, trc); break;
1043
+ case JS_CELL_VALUE_ARRAY: visit_value_array(c, trc); break;
1044
+ case JS_CELL_ELEMENTS: visit_elements(c, trc); break;
1045
+ case JS_CELL_STR_ROPE: visit_rope(c, trc); break;
1046
+ case JS_CELL_STR_SLICE: visit_slice(c, trc); break;
1047
+ case JS_CELL_SHAPE: visit_shape(c, trc); break;
1048
+ case JS_CELL_ACCESSOR: visit_accessor(c, trc); break;
1049
+ case JS_CELL_SYMBOL: visit_symbol(c, trc); break;
1050
+ case JS_CELL_HOST: ((JSHostCell *)c)->cls->visit(c, trc); break; /* mark() pushes only those with a visit */
1051
+ default: js_fatal("GC: a cell of kind %d on the mark stack", c->kind);
1052
+ }
1053
+ }
1054
+
1055
+ /* ---- Parallel marking -------------------------------------------------- */
1056
+
1057
+ static void flush_fifo(JSTracer *trc);
1058
+ static bool visit_range(JSTracer *trc, JSCell *c, size_t from, size_t to);
1059
+
1060
+ /* Helper threads join a pause's marking once the marker has visited this many cells: below it,
1061
+ * waking them costs more than they'd do. Markers hand each other cells in packets, through a pool
1062
+ * under a lock: while another marker waits, one with work gives half its stack away (PACKET at
1063
+ * most), from the bottom, where the oldest cells are: a tree's big subtrees, not its leaves. A
1064
+ * marker with none takes a packet. The marking ends when every marker waits and none is left. */
1065
+ #define PACKET 512
1066
+ #define PARALLEL_AFTER 32768
1067
+ /* A large cell is visited this much at a time: by a slice (incremental marking), or a marker that
1068
+ * shares the rest with the others; and one this large brings the helpers in at once. */
1069
+ #define MARK_CHUNK ((size_t)64 << 10)
1070
+ #define PARALLEL_CELL (4 * MARK_CHUNK)
1071
+ #define MAX_HELPERS 15
1072
+
1073
+ typedef struct Packet {
1074
+ struct Packet *next;
1075
+ uint32_t count;
1076
+ JSCell *cells[PACKET];
1077
+ } Packet;
1078
+
1079
+ typedef struct JSMarkHelper {
1080
+ struct JSMarkPool *pool;
1081
+ JSOsThread *thread;
1082
+ JSTracer trc;
1083
+ uint64_t joined; /* the marking it joined last */
1084
+ } JSMarkHelper;
1085
+
1086
+ /* A part of a large cell to visit: a young collection's marked cards, shared out before the
1087
+ * markers steal from each other. */
1088
+ typedef struct MarkTask {
1089
+ JSCell *cell;
1090
+ size_t from, to;
1091
+ } MarkTask;
1092
+
1093
+ typedef struct JSMarkPool {
1094
+ JSOsLock *lock;
1095
+ JSOsCond *wake; /* helpers: a marking to join, or the runtime's end */
1096
+ JSOsCond *work; /* markers: a packet, or the marking's end */
1097
+ Packet *full, *spare; /* packets of cells to visit; empty ones, for reuse */
1098
+ unsigned helpers;
1099
+ unsigned markers, idle; /* this marking's: markers that joined it, those waiting */
1100
+ int waiting; /* idle, read without the lock: whether to give a packet away */
1101
+ int news; /* bumped when work comes or the marking ends: what a spinning marker watches */
1102
+ bool done, quit;
1103
+ uint64_t epoch; /* a new marking for the helpers */
1104
+ JSMarkHelper *helper;
1105
+ MarkTask *tasks; /* this marking's, taken in turn (task_next) by every marker first */
1106
+ size_t ntasks, task_cap, task_next;
1107
+ } JSMarkPool;
1108
+
1109
+ static void add_task(JSMarkPool *p, JSCell *c, size_t from, size_t to) {
1110
+ if (p->ntasks == p->task_cap) {
1111
+ p->task_cap = p->task_cap ? p->task_cap * 2 : 256;
1112
+ p->tasks = xrealloc(p->tasks, p->task_cap * sizeof *p->tasks);
1113
+ }
1114
+ p->tasks[p->ntasks++] = (MarkTask){ c, from, to };
1115
+ atomic_store_int(&p->news, p->news + 1);
1116
+ }
1117
+
1118
+ static void give_packet(JSTracer *trc, JSMarkPool *p) {
1119
+ size_t n = (trc->sp - trc->base) / 2;
1120
+ if (n > PACKET) n = PACKET;
1121
+ js_os_lock(p->lock);
1122
+ Packet *k = p->spare;
1123
+ if (k) p->spare = k->next;
1124
+ else k = xrealloc(NULL, sizeof *k);
1125
+ k->count = (uint32_t)n;
1126
+ memcpy(k->cells, &trc->stack[trc->base], n * sizeof *k->cells);
1127
+ trc->base += n;
1128
+ k->next = p->full;
1129
+ p->full = k;
1130
+ atomic_store_int(&p->news, p->news + 1);
1131
+ js_os_cond_signal(p->work);
1132
+ js_os_unlock(p->lock);
509
1133
  }
510
1134
 
511
- /* Explicit stack, never recursion: ropes and linked lists can be very deep. */
1135
+ #if defined(__x86_64__) || defined(__i386__)
1136
+ #define CPU_RELAX() __builtin_ia32_pause()
1137
+ #else
1138
+ #define CPU_RELAX() ((void)0)
1139
+ #endif
1140
+
1141
+ /* Waits for work: a packet onto the stack (1), or a task (2, in *task); 0 when marking `epoch` is
1142
+ * over: every marker that joined it waits, and nothing is left. A marker spins a little first:
1143
+ * work often comes within microseconds, and a thread put to sleep takes much longer to come
1144
+ * back. */
1145
+ static int take_work(JSTracer *trc, JSMarkPool *p, uint64_t epoch, MarkTask *task) {
1146
+ js_os_lock(p->lock);
1147
+ for (;;) {
1148
+ /* woken late: that marking is over, and the next one's work (posted before its epoch) isn't ours */
1149
+ if (p->epoch != epoch || p->done) break;
1150
+ if (p->task_next < p->ntasks) {
1151
+ *task = p->tasks[p->task_next++];
1152
+ js_os_unlock(p->lock);
1153
+ return 2;
1154
+ }
1155
+ Packet *k = p->full;
1156
+ if (k) {
1157
+ p->full = k->next;
1158
+ while (trc->sp + k->count > trc->cap) grow_stack(trc);
1159
+ memcpy(&trc->stack[trc->sp], k->cells, k->count * sizeof *k->cells);
1160
+ trc->sp += k->count;
1161
+ k->next = p->spare;
1162
+ p->spare = k;
1163
+ js_os_unlock(p->lock);
1164
+ return 1;
1165
+ }
1166
+ p->idle++;
1167
+ atomic_store_int(&p->waiting, (int)p->idle);
1168
+ if (p->idle == p->markers) {
1169
+ p->done = true;
1170
+ atomic_store_int(&p->news, p->news + 1);
1171
+ js_os_cond_broadcast(p->work);
1172
+ break;
1173
+ }
1174
+ int news = p->news;
1175
+ js_os_unlock(p->lock);
1176
+ for (int spin = 0; spin < 2000 && atomic_load_int(&p->news) == news; spin++) CPU_RELAX();
1177
+ js_os_lock(p->lock);
1178
+ if (p->epoch != epoch) break;
1179
+ if (!p->full && !p->done && p->task_next >= p->ntasks) js_os_cond_wait(p->work, p->lock);
1180
+ if (p->epoch != epoch) break;
1181
+ p->idle--;
1182
+ atomic_store_int(&p->waiting, (int)p->idle);
1183
+ }
1184
+ js_os_unlock(p->lock);
1185
+ return 0;
1186
+ }
1187
+
1188
+ /* The slots' bytes of a cell whose visit can be split (exact range visits), else 0: computed
1189
+ * from the cell itself, not its block's header, which would be one more cache miss per visit. */
1190
+ static size_t splittable_bytes(const JSCell *c) {
1191
+ switch (c->kind) {
1192
+ case JS_CELL_VALUE_ARRAY: case JS_CELL_ENV: return (size_t)c->aux * sizeof(JSValue);
1193
+ case JS_CELL_ELEMENTS:
1194
+ return ((const JSElements *)c)->kind == JS_ELEMENTS_DENSE ? (size_t)((const JSElements *)c)->length * sizeof(JSValue) : 0;
1195
+ default: return 0;
1196
+ }
1197
+ }
1198
+
1199
+ /* A large cell met while marking with others: its chunks but the first become tasks, for
1200
+ * whoever is free; this marker visits the first. */
1201
+ static void split_cell(JSTracer *trc, JSMarkPool *p, JSCell *c) {
1202
+ size_t size = js_block_of(c)->cell_size;
1203
+ js_os_lock(p->lock);
1204
+ for (size_t from = MARK_CHUNK; from < size; from += MARK_CHUNK)
1205
+ add_task(p, c, from, size - from > MARK_CHUNK ? from + MARK_CHUNK : size);
1206
+ js_os_cond_broadcast(p->work);
1207
+ js_os_unlock(p->lock);
1208
+ #ifdef JS_GC_VERIFY
1209
+ trc->visiting = c;
1210
+ #endif
1211
+ visit_range(trc, c, 0, MARK_CHUNK);
1212
+ }
1213
+
1214
+ /* One marker's part, the main one's or a helper's, until marking `epoch` ends: its stack, then
1215
+ * tasks and what it takes from the others'. */
1216
+ static void mark_with_others(JSTracer *trc, JSMarkPool *p, uint64_t epoch) {
1217
+ MarkTask task;
1218
+ for (;;) {
1219
+ while (trc->sp > trc->base) {
1220
+ JSCell *c = trc->stack[--trc->sp];
1221
+ if (splittable_bytes(c) >= 2 * MARK_CHUNK) split_cell(trc, p, c);
1222
+ else visit(trc, c);
1223
+ if (trc->sp - trc->base >= 8 && atomic_load_int(&p->waiting)) give_packet(trc, p);
1224
+ }
1225
+ trc->sp = trc->base = 0;
1226
+ flush_fifo(trc);
1227
+ if (trc->sp) continue;
1228
+ int got = take_work(trc, p, epoch, &task);
1229
+ if (!got) break;
1230
+ if (got == 2) {
1231
+ #ifdef JS_GC_VERIFY
1232
+ trc->visiting = task.cell;
1233
+ #endif
1234
+ visit_range(trc, task.cell, task.from, task.to);
1235
+ }
1236
+ }
1237
+ #ifdef JS_GC_VERIFY
1238
+ trc->visiting = NULL;
1239
+ #endif
1240
+ }
1241
+
1242
+ /* A helper joins each marking it wakes up for in time: one already done goes on without it. */
1243
+ static void helper_main(void *arg) {
1244
+ JSMarkHelper *me = arg;
1245
+ JSMarkPool *p = me->pool;
1246
+ uint64_t seen = 0;
1247
+ js_os_lock(p->lock);
1248
+ for (;;) {
1249
+ while (!p->quit && p->epoch == seen) js_os_cond_wait(p->wake, p->lock);
1250
+ if (p->quit) break;
1251
+ seen = p->epoch;
1252
+ if (p->done) continue;
1253
+ p->markers++;
1254
+ me->trc.marked = me->trc.marked_bytes = 0;
1255
+ me->trc.parallel = true;
1256
+ me->joined = seen;
1257
+ js_os_unlock(p->lock);
1258
+ mark_with_others(&me->trc, p, seen);
1259
+ js_os_lock(p->lock);
1260
+ }
1261
+ js_os_unlock(p->lock);
1262
+ }
1263
+
1264
+ /* The helpers, started at the first marking that needs them; NULL when there are none
1265
+ * (JS_GC_THREADS=0, one processor, or the threads couldn't start). */
1266
+ static JSMarkPool *mark_pool(JSHeap *h) {
1267
+ if (h->pool || !h->gc_helpers) return h->pool;
1268
+ JSMarkPool *p = xrealloc(NULL, sizeof *p);
1269
+ memset(p, 0, sizeof *p);
1270
+ p->lock = js_os_lock_new();
1271
+ p->wake = js_os_cond_new();
1272
+ p->work = js_os_cond_new();
1273
+ p->helper = xrealloc(NULL, h->gc_helpers * sizeof *p->helper);
1274
+ memset(p->helper, 0, h->gc_helpers * sizeof *p->helper);
1275
+ h->pool = p;
1276
+ if (!p->lock || !p->wake || !p->work) {
1277
+ h->gc_helpers = 0;
1278
+ return NULL;
1279
+ }
1280
+ for (unsigned i = 0; i < h->gc_helpers; i++) {
1281
+ p->helper[i].pool = p;
1282
+ p->helper[i].trc.heap = h;
1283
+ p->helper[i].thread = js_os_thread_start(helper_main, &p->helper[i]);
1284
+ if (!p->helper[i].thread) break;
1285
+ p->helpers++;
1286
+ }
1287
+ if (!p->helpers) h->gc_helpers = 0;
1288
+ return p->helpers ? p : NULL;
1289
+ }
1290
+
1291
+ static void free_mark_pool(JSHeap *h) {
1292
+ JSMarkPool *p = h->pool;
1293
+ if (!p) return;
1294
+ if (p->lock) {
1295
+ js_os_lock(p->lock);
1296
+ p->quit = true;
1297
+ js_os_cond_broadcast(p->wake);
1298
+ js_os_unlock(p->lock);
1299
+ }
1300
+ for (unsigned i = 0; i < p->helpers; i++) {
1301
+ js_os_thread_join(p->helper[i].thread);
1302
+ free(p->helper[i].trc.stack);
1303
+ }
1304
+ for (Packet *k = p->spare, *next; k; k = next) {
1305
+ next = k->next;
1306
+ free(k);
1307
+ }
1308
+ if (p->lock) js_os_lock_free(p->lock);
1309
+ if (p->wake) js_os_cond_free(p->wake);
1310
+ if (p->work) js_os_cond_free(p->work);
1311
+ free(p->helper);
1312
+ free(p->tasks);
1313
+ free(p);
1314
+ h->pool = NULL;
1315
+ }
1316
+
1317
+ /* Marks what's left with the helpers. Returns false, having done nothing, when there are none. */
1318
+ static bool drain_in_parallel(JSTracer *trc) {
1319
+ JSHeap *h = trc->heap;
1320
+ JSMarkPool *p = mark_pool(h);
1321
+ if (!p) return false;
1322
+ js_os_lock(p->lock);
1323
+ uint64_t epoch = ++p->epoch;
1324
+ p->markers = 1; /* the helpers count themselves in as they join */
1325
+ p->idle = 0;
1326
+ atomic_store_int(&p->waiting, 0);
1327
+ p->done = false;
1328
+ trc->parallel = true;
1329
+ js_os_cond_broadcast(p->wake);
1330
+ js_os_unlock(p->lock);
1331
+
1332
+ mark_with_others(trc, p, epoch);
1333
+
1334
+ /* done: every marker that joined waits, so their counts are final; the others didn't mark */
1335
+ js_os_lock(p->lock);
1336
+ for (unsigned i = 0; i < p->helpers; i++) {
1337
+ JSMarkHelper *m = &p->helper[i];
1338
+ if (m->joined != epoch) continue;
1339
+ trc->marked += m->trc.marked;
1340
+ trc->marked_bytes += m->trc.marked_bytes;
1341
+ }
1342
+ p->ntasks = p->task_next = 0;
1343
+ js_os_unlock(p->lock);
1344
+ trc->parallel = false;
1345
+ h->stats.parallel_markings++;
1346
+ return true;
1347
+ }
1348
+
1349
+ /* Until there's nothing left: visits the marked cells on the stack, and when it's empty marks
1350
+ * the edges still waiting in the FIFO. Explicit stack, never recursion: ropes and linked lists
1351
+ * can be very deep. With much to visit, the helpers join (in a pause; a slice marks alone). */
512
1352
  static void drain(JSTracer *trc) {
513
- while (trc->sp) {
514
- JSCell *c = trc->heap->mark_stack[--trc->sp];
515
- const JSCellClass *cls = class_of(c);
516
- if (cls->visit) cls->visit(c, trc);
1353
+ JSMarkPool *p = trc->heap->pool;
1354
+ if (p && p->ntasks && drain_in_parallel(trc)) return;
1355
+ size_t visited = 0;
1356
+ for (;;) {
1357
+ while (trc->sp) {
1358
+ JSCell *c = trc->stack[trc->sp - 1];
1359
+ if (trc->heap->gc_helpers && ((++visited > PARALLEL_AFTER && trc->sp >= 2) || splittable_bytes(c) >= PARALLEL_CELL) &&
1360
+ drain_in_parallel(trc))
1361
+ return;
1362
+ trc->sp--;
1363
+ visit(trc, c);
1364
+ }
1365
+ if (!trc->fifo_count) break;
1366
+ JSCell *c = trc->fifo[trc->fifo_head];
1367
+ trc->fifo_head = (trc->fifo_head + 1) % MARK_FIFO;
1368
+ trc->fifo_count--;
1369
+ mark(trc, c);
1370
+ }
1371
+ #ifdef JS_GC_VERIFY
1372
+ trc->visiting = NULL;
1373
+ #endif
1374
+ }
1375
+
1376
+ static bool has_children(const JSCell *c) {
1377
+ return !leaf_kind[c->kind] && !(c->kind == JS_CELL_HOST && !((const JSHostCell *)c)->cls->visit);
1378
+ }
1379
+
1380
+ /* The slots of `slots[0..n)` (at byte `base` of their cell) in bytes [from, to) of it. */
1381
+ static void trace_slots_in(JSTracer *trc, const JSValue *slots, size_t n, size_t base, size_t from, size_t to) {
1382
+ size_t first = from <= base ? 0 : (from - base) / sizeof(JSValue);
1383
+ size_t last = to <= base ? 0 : (to - base + sizeof(JSValue) - 1) / sizeof(JSValue);
1384
+ for (size_t i = first; i < last && i < n; i++) trace_value(trc, slots[i]);
1385
+ }
1386
+
1387
+ /* Traces what large `c` holds in bytes [from, to) of it, at least; the whole cell for a kind
1388
+ * that can't tell. Returns whether it was exact: only what's in the range. */
1389
+ static bool visit_range(JSTracer *trc, JSCell *c, size_t from, size_t to) {
1390
+ switch (c->kind) {
1391
+ case JS_CELL_VALUE_ARRAY:
1392
+ trace_slots_in(trc, ((JSValueArray *)c)->values, c->aux, offsetof(JSValueArray, values), from, to);
1393
+ return true;
1394
+ case JS_CELL_ELEMENTS: {
1395
+ JSElements *e = (JSElements *)c;
1396
+ if (e->kind != JS_ELEMENTS_DENSE) break; /* a sparse store's entries are malloc'd */
1397
+ trace_slots_in(trc, e->data, e->length, offsetof(JSElements, data), from, to);
1398
+ return true;
1399
+ }
1400
+ case JS_CELL_ENV: {
1401
+ JSEnv *e = (JSEnv *)c;
1402
+ if (from < offsetof(JSEnv, vars) && e->parent) trace_cell(trc, &e->parent->cell);
1403
+ trace_slots_in(trc, e->vars, c->aux, offsetof(JSEnv, vars), from, to);
1404
+ return true;
1405
+ }
1406
+ case JS_CELL_HOST: {
1407
+ const JSCellClass *cls = ((JSHostCell *)c)->cls;
1408
+ if (!cls->visit_range) break;
1409
+ cls->visit_range(c, trc, from, to);
1410
+ return false; /* a class may trace a little more than the range */
1411
+ }
1412
+ default:
1413
+ break;
1414
+ }
1415
+ if (has_children(c)) visit(trc, c);
1416
+ return false;
1417
+ }
1418
+
1419
+ /* A remembered large cell: visits its marked cards, a run of them at a time (`marked`), or, for
1420
+ * the barrier verifier, the others; then, when visiting the marked ones, forgets them. */
1421
+ static void visit_cards(JSTracer *trc, JSCell *c, bool marked) {
1422
+ JSBlock *b = js_block_of(c);
1423
+ uint64_t *cards = js_large_cards(b);
1424
+ unsigned shift = b->size_class;
1425
+ size_t n = (b->cell_size + ((size_t)1 << shift) - 1) >> shift;
1426
+ for (size_t k = 0; k < n;) {
1427
+ if ((bool)((cards[k / 64] >> (k % 64)) & 1) != marked) {
1428
+ k++;
1429
+ continue;
1430
+ }
1431
+ size_t end = k + 1;
1432
+ while (end < n && (bool)((cards[end / 64] >> (end % 64)) & 1) == marked) end++;
1433
+ #ifdef JS_GC_VERIFY
1434
+ trc->visiting = c;
1435
+ #endif
1436
+ size_t to = end << shift < b->cell_size ? end << shift : b->cell_size;
1437
+ if (!visit_range(trc, c, k << shift, to) && !marked) return; /* inexact: can't check part */
1438
+ k = end;
1439
+ }
1440
+ if (marked) {
1441
+ memset(cards, 0, JS_LARGE_CARDS / 8);
1442
+ b->remembered = 0;
1443
+ }
1444
+ }
1445
+
1446
+ /* ---- Incremental marking: slices ---------------------------------------- */
1447
+
1448
+ /* A large cell is visited this much at a time in a slice, when its kind's range visit is exact. */
1449
+
1450
+ static bool visits_in_chunks(const JSCell *c) {
1451
+ if (!js_block_of(c)->is_large || js_block_of(c)->cell_size <= MARK_CHUNK) return false;
1452
+ return c->kind == JS_CELL_VALUE_ARRAY || c->kind == JS_CELL_ENV ||
1453
+ (c->kind == JS_CELL_ELEMENTS && ((const JSElements *)c)->kind == JS_ELEMENTS_DENSE);
1454
+ }
1455
+
1456
+ /* The next chunk of the partly visited cell. Returns the bytes it covered. */
1457
+ static size_t visit_chunk(JSTracer *trc) {
1458
+ JSCell *c = trc->partial;
1459
+ size_t size = js_block_of(c)->cell_size, from = trc->partial_from;
1460
+ size_t to = size - from > MARK_CHUNK ? from + MARK_CHUNK : size;
1461
+ #ifdef JS_GC_VERIFY
1462
+ trc->visiting = c;
1463
+ #endif
1464
+ visit_range(trc, c, from, to);
1465
+ trc->partial_from = to;
1466
+ if (to == size) trc->partial = NULL;
1467
+ return to - from;
1468
+ }
1469
+
1470
+ /* Marks the edges waiting in the FIFO: between slices, what the marker found is on its stack. */
1471
+ static void flush_fifo(JSTracer *trc) {
1472
+ while (trc->fifo_count) {
1473
+ JSCell *c = trc->fifo[trc->fifo_head];
1474
+ trc->fifo_head = (trc->fifo_head + 1) % MARK_FIFO;
1475
+ trc->fifo_count--;
1476
+ mark(trc, c);
517
1477
  }
518
1478
  }
519
1479
 
1480
+ /* A slice: visits marked cells until about `budget` bytes were marked or scanned, or there are
1481
+ * none left (then returns true). */
1482
+ static bool drain_some(JSTracer *trc, size_t budget) {
1483
+ size_t start = trc->marked_bytes, scanned = 0;
1484
+ for (;;) {
1485
+ while (trc->marked_bytes - start + scanned < budget) {
1486
+ if (trc->partial) {
1487
+ scanned += visit_chunk(trc);
1488
+ continue;
1489
+ }
1490
+ if (!trc->sp) break;
1491
+ JSCell *c = trc->stack[--trc->sp];
1492
+ if (visits_in_chunks(c)) {
1493
+ trc->partial = c;
1494
+ trc->partial_from = 0;
1495
+ continue;
1496
+ }
1497
+ visit(trc, c);
1498
+ }
1499
+ flush_fifo(trc);
1500
+ if (!trc->sp && !trc->partial) return true;
1501
+ if (trc->marked_bytes - start + scanned >= budget) return false;
1502
+ }
1503
+ }
1504
+
1505
+ /* Everything left: the final pause's. */
1506
+ static void drain_all(JSTracer *trc) {
1507
+ while (trc->partial) visit_chunk(trc);
1508
+ drain(trc);
1509
+ }
1510
+
1511
+ /* A young collection's other roots: the old cells a store gave a young cell. They're marked
1512
+ * already, so they're pushed to be visited, dead or alive: a remembered cell that died since keeps
1513
+ * its young cells until the next collection, so nothing it points to is freed under it (a stale
1514
+ * stack word could still find it). Each becomes plain old again. */
1515
+ /* Below this much in marked cards, a young collection visits them itself. */
1516
+ #define PARALLEL_CARDS ((size_t)256 << 10)
1517
+
1518
+ static bool exact_range(const JSCell *c) {
1519
+ switch (c->kind) {
1520
+ case JS_CELL_VALUE_ARRAY: case JS_CELL_ENV: return true;
1521
+ case JS_CELL_ELEMENTS: return ((const JSElements *)c)->kind == JS_ELEMENTS_DENSE;
1522
+ case JS_CELL_HOST: return ((const JSHostCell *)c)->cls->visit_range != NULL;
1523
+ default: return false;
1524
+ }
1525
+ }
1526
+
1527
+
1528
+ /* Large `c`'s marked card runs, as tasks of MARK_CHUNK bytes at most (the whole cell, once, for a
1529
+ * kind that can't visit part of it); then its cards are forgotten. Returns the tasks' bytes. */
1530
+ static size_t card_tasks(JSMarkPool *p, JSCell *c) {
1531
+ JSBlock *b = js_block_of(c);
1532
+ uint64_t *cards = js_large_cards(b);
1533
+ unsigned shift = b->size_class;
1534
+ size_t n = (b->cell_size + ((size_t)1 << shift) - 1) >> shift, bytes = 0;
1535
+ if (!exact_range(c)) {
1536
+ add_task(p, c, 0, b->cell_size);
1537
+ bytes = b->cell_size;
1538
+ } else {
1539
+ for (size_t k = 0; k < n;) {
1540
+ if (!((cards[k / 64] >> (k % 64)) & 1)) {
1541
+ k++;
1542
+ continue;
1543
+ }
1544
+ size_t end = k + 1;
1545
+ while (end < n && ((cards[end / 64] >> (end % 64)) & 1)) end++;
1546
+ size_t to = end << shift < b->cell_size ? end << shift : b->cell_size;
1547
+ for (size_t from = k << shift; from < to; from += MARK_CHUNK) {
1548
+ add_task(p, c, from, to - from > MARK_CHUNK ? from + MARK_CHUNK : to);
1549
+ bytes += to - from > MARK_CHUNK ? MARK_CHUNK : to - from;
1550
+ }
1551
+ k = end;
1552
+ }
1553
+ }
1554
+ memset(cards, 0, JS_LARGE_CARDS / 8);
1555
+ b->remembered = 0;
1556
+ return bytes;
1557
+ }
1558
+
1559
+ /* A young collection's other roots: the remembered cells. With helpers, large cells' marked cards
1560
+ * become tasks that every marker takes a share of (drain), when there are enough of them. */
1561
+ static void mark_remembered(JSHeap *h, JSTracer *trc) {
1562
+ JSMarkPool *p = h->gc_helpers ? mark_pool(h) : NULL;
1563
+ size_t task_bytes = 0;
1564
+ if (p) js_os_lock(p->lock); /* a helper may still be leaving the last marking */
1565
+ for (size_t i = 0; i < h->remembered_count; i++) {
1566
+ JSCell *c = h->remembered[i];
1567
+ if (c->gc == JS_GC_OLD_LARGE) {
1568
+ if (p) task_bytes += card_tasks(p, c);
1569
+ else visit_cards(trc, c, true);
1570
+ continue;
1571
+ }
1572
+ c->gc = JS_GC_OLD;
1573
+ if (has_children(c)) push(trc, c);
1574
+ }
1575
+ if (p) js_os_unlock(p->lock);
1576
+ if (p && task_bytes < PARALLEL_CARDS) { /* not worth waking the helpers for */
1577
+ for (size_t i = 0; i < p->ntasks; i++) {
1578
+ #ifdef JS_GC_VERIFY
1579
+ trc->visiting = p->tasks[i].cell;
1580
+ #endif
1581
+ visit_range(trc, p->tasks[i].cell, p->tasks[i].from, p->tasks[i].to);
1582
+ }
1583
+ p->ntasks = 0;
1584
+ }
1585
+ }
1586
+
1587
+ /* Forgets the remembered set: a full collection marks everything. */
1588
+ static void forget_remembered(JSHeap *h) {
1589
+ for (size_t i = 0; i < h->remembered_count; i++) {
1590
+ JSCell *c = h->remembered[i];
1591
+ if (c->gc != JS_GC_OLD_LARGE) continue; /* JS_GC_OLD again when it's marked */
1592
+ JSBlock *b = js_block_of(c);
1593
+ memset(js_large_cards(b), 0, JS_LARGE_CARDS / 8);
1594
+ b->remembered = 0;
1595
+ }
1596
+ h->remembered_count = 0;
1597
+ }
1598
+
1599
+ #ifdef JS_GC_VERIFY
1600
+ /* Calls `f` on every allocated old cell that isn't remembered. */
1601
+ static void each_old_cell(JSHeap *h, void (*f)(JSTracer *, JSCell *), JSTracer *trc) {
1602
+ for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++)
1603
+ for (JSBlock *b = h->blocks[sc]; b; b = b->next)
1604
+ for (uint32_t w = 0; w < (b->cell_count + 63) / 64; w++)
1605
+ for (uint64_t m = b->alloc_bits[w] & b->mark_bits[w]; m; m &= m - 1) {
1606
+ JSCell *c = (JSCell *)block_cell(b, w * 64 + js_ctz64(m));
1607
+ if (c->gc >= JS_GC_OLD) f(trc, c);
1608
+ }
1609
+ for (size_t i = 0; i < h->large_count; i++) {
1610
+ JSCell *c = (JSCell *)block_cell(h->large[i], 0);
1611
+ if ((h->large[i]->mark_bits[0] & 1) && c->gc >= JS_GC_OLD) f(trc, c);
1612
+ }
1613
+ }
1614
+
1615
+ static void check_old_cell(JSTracer *trc, JSCell *c) {
1616
+ if (c->gc == JS_GC_OLD_LARGE && js_block_of(c)->remembered) visit_cards(trc, c, false); /* the unmarked cards */
1617
+ else if (has_children(c)) visit(trc, c);
1618
+ }
1619
+
1620
+ /* The barrier verifier, before a young collection: an old cell that isn't remembered, dead or
1621
+ * alive, points only to old cells. A store that skipped the barrier stops here, naming the old
1622
+ * cell, at the first collection that could have freed what it stored. */
1623
+ static void verify_remembered(JSHeap *h, JSTracer *trc) {
1624
+ trc->check_old = true;
1625
+ each_old_cell(h, check_old_cell, trc);
1626
+ trc->check_old = false;
1627
+ trc->visiting = NULL;
1628
+ }
1629
+
1630
+ /* The marking barrier's verifier, in the final pause of an incremental marking, before the roots
1631
+ * and the stack are scanned again: once the marker has visited everything it found, a marked
1632
+ * cell points only to marked cells. One that doesn't was given a cell without the barrier,
1633
+ * after the marker visited it: that cell would be freed while it's reachable. */
1634
+ static void verify_marking(JSHeap *h, JSTracer *trc) {
1635
+ trc->check_old = trc->check_black = true;
1636
+ each_old_cell(h, check_old_cell, trc);
1637
+ trc->check_old = trc->check_black = false;
1638
+ trc->visiting = NULL;
1639
+ }
1640
+ #endif
1641
+
520
1642
  static void mark_roots(JSRuntime *rt, JSTracer *trc) {
521
1643
  JSHeap *h = &rt->heap;
522
- js_trace_value(trc, rt->exception); /* ADR 0004, invariant 4 */
1644
+ js_trace_value(trc, rt->exception); /* the pending exception is a root */
523
1645
  js_trace_value(trc, rt->symbol_registry);
524
1646
  js_trace_value(trc, rt->async_context);
525
- js_jobs_trace(rt, trc); /* queued jobs, unhandled rejections (ADR 0008) */
1647
+ js_jobs_trace(rt, trc); /* queued jobs, unhandled rejections */
526
1648
  js_weak_trace_roots(rt, trc); /* the WeakRef kept-alive set */
527
1649
  for (JSContext *realm = rt->realms; realm; realm = realm->next_realm)
528
1650
  for (unsigned i = 0; i < JS_INTRINSIC_COUNT; i++) js_trace_value(trc, realm->intrinsics[i]);
@@ -556,6 +1678,7 @@ static JSCell *find_cell(const JSHeap *h, uintptr_t w) {
556
1678
  if (w < base + JS_BLOCK_FIRST_CELL) return NULL;
557
1679
  i = (uint32_t)((w - base - JS_BLOCK_FIRST_CELL) / b->cell_size);
558
1680
  if (i >= b->cell_count) return NULL;
1681
+ if (b->queued) return bit_get(b->mark_bits, i) ? (JSCell *)block_cell(b, i) : NULL; /* unswept */
559
1682
  } else {
560
1683
  b = find_large(h, w);
561
1684
  if (!b || w < (uintptr_t)b + JS_BLOCK_FIRST_CELL) return NULL;
@@ -564,13 +1687,6 @@ static JSCell *find_cell(const JSHeap *h, uintptr_t w) {
564
1687
  return bit_get(b->alloc_bits, i) ? (JSCell *)block_cell(b, i) : NULL;
565
1688
  }
566
1689
 
567
- #if defined(__clang__) || defined(__GNUC__)
568
- #define JS_NOINLINE __attribute__((noinline))
569
- #define JS_NO_ASAN __attribute__((no_sanitize_address))
570
- #else
571
- #define JS_NOINLINE __declspec(noinline)
572
- #define JS_NO_ASAN
573
- #endif
574
1690
 
575
1691
  #if defined(__SANITIZE_ADDRESS__)
576
1692
  #define JS_ASAN 1
@@ -602,61 +1718,57 @@ static JS_NO_ASAN void mark_stack(JSTracer *trc) {
602
1718
 
603
1719
  /* ---- Collection -------------------------------------------------------- */
604
1720
 
605
- /* Makes the allocation bits exact: cells still on a free list aren't allocated. */
1721
+ /* Makes the allocation bits exact: the cells left in each class's window weren't handed out. */
606
1722
  static void stop_allocation(JSHeap *h) {
607
1723
  for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++) {
608
- JSBlock *b = h->current[sc];
609
- for (JSFreeCell *c = h->free[sc]; c; c = c->next) bit_clear(b->alloc_bits, cell_index(b, c));
610
- h->free[sc] = NULL;
611
- h->current[sc] = NULL;
1724
+ JSAllocator *a = &h->alloc[sc];
1725
+ if (a->free) {
1726
+ a->block->alloc_bits[a->word - 1] &= ~a->free;
1727
+ h->allocated_since_gc -= (size_t)popcount64(a->free) * a->cell_size;
1728
+ }
1729
+ a->free = 0;
1730
+ a->block = NULL;
612
1731
  }
613
1732
  }
614
1733
 
615
- /* Unswept blocks may hold dead cells whose children are already reused.
616
- * Sweep them before marking so that nothing can resurrect such a cell. */
1734
+ /* Unswept blocks may hold dead cells whose children are already reused: a full collection sweeps
1735
+ * them before marking (it clears the mark bits) so that nothing can resurrect such a cell. A young
1736
+ * collection doesn't need to: an unswept block holds no young cell, and its mark bits say which
1737
+ * of its cells are alive (find_cell). */
617
1738
  static void finish_sweeping(JSRuntime *rt) {
618
- JSFreeCell *list;
619
- uint32_t n;
1739
+ JSHeap *h = &rt->heap;
1740
+ JSBlock *b;
620
1741
  for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++)
621
- while (sweep_next(rt, sc, &list, &n)) {}
1742
+ while (sweep_next(rt, sc, false, &b))
1743
+ if (b) list_push(&h->ready[sc], b); /* free cells: the allocator's, swept */
622
1744
  }
623
1745
 
624
- static unsigned popcount64(uint64_t x) {
625
- #if defined(_MSC_VER) && !defined(__clang__)
626
- return (unsigned)__popcnt64(x);
627
- #else
628
- return (unsigned)__builtin_popcountll(x);
629
- #endif
630
- }
631
-
632
- static size_t marked_bytes(const JSHeap *h) {
633
- size_t bytes = 0;
634
- for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++)
635
- for (JSBlock *b = h->blocks[sc]; b; b = b->next) {
636
- size_t cells = 0;
637
- for (unsigned w = 0; w < JS_BITMAP_WORDS; w++) cells += popcount64(b->mark_bits[w]);
638
- bytes += cells * b->cell_size;
639
- }
640
- return bytes + h->large_bytes; /* after sweep_large: only survivors remain */
641
- }
642
1746
 
643
1747
  /* Runs the finalizers of the dead cells, all of them, before any memory is freed or reused. A
644
1748
  * finalizer may read other cells: an object's reaches its class through its shape, which may be
645
1749
  * dead too, and which lazy sweeping could otherwise free and reuse before the object's own block
646
1750
  * is swept. Each finalized cell loses JS_CELL_FINALIZABLE, so nothing finalizes it twice. */
647
- static void finalize_dead(JSRuntime *rt) {
648
- JSHeap *h = &rt->heap;
649
- for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++)
650
- for (JSBlock *b = h->blocks[sc]; b; b = b->next) {
651
- if (!b->has_finalizable) continue;
652
- for (uint32_t i = 0; i < b->cell_count; i++) {
653
- JSCell *c = (JSCell *)block_cell(b, i);
654
- if (bit_get(b->alloc_bits, i) && !bit_get(b->mark_bits, i) && (c->flags & JS_CELL_FINALIZABLE)) {
655
- finalize_cell(rt, c);
656
- c->flags &= (uint16_t)~JS_CELL_FINALIZABLE;
657
- }
1751
+ static void finalize_dead_in(JSRuntime *rt, JSBlock *b) {
1752
+ if (!b->has_finalizable) return;
1753
+ for (uint32_t w = 0; w < (b->cell_count + 63) / 64; w++)
1754
+ for (uint64_t dead = b->alloc_bits[w] & ~b->mark_bits[w]; dead; dead &= dead - 1) {
1755
+ JSCell *c = (JSCell *)block_cell(b, w * 64 + js_ctz64(dead));
1756
+ if (c->flags & JS_CELL_FINALIZABLE) {
1757
+ finalize_cell(rt, c);
1758
+ c->flags &= (uint16_t)~JS_CELL_FINALIZABLE;
658
1759
  }
659
1760
  }
1761
+ }
1762
+
1763
+ /* After a young collection only young cells died: they're in the blocks the allocator took. */
1764
+ static void finalize_dead(JSRuntime *rt, bool full) {
1765
+ JSHeap *h = &rt->heap;
1766
+ if (full) {
1767
+ for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++)
1768
+ for (JSBlock *b = h->blocks[sc]; b; b = b->next) finalize_dead_in(rt, b);
1769
+ } else {
1770
+ for (size_t i = 0; i < h->young_blocks.count; i++) finalize_dead_in(rt, h->young_blocks.items[i]);
1771
+ }
660
1772
  for (size_t i = 0; i < h->large_count; i++) {
661
1773
  JSBlock *b = h->large[i];
662
1774
  JSCell *c = (JSCell *)block_cell(b, 0);
@@ -667,59 +1779,416 @@ static void finalize_dead(JSRuntime *rt) {
667
1779
  }
668
1780
  }
669
1781
 
670
- static JS_NOINLINE void collect(JSContext *ctx, const void *stack_low) {
1782
+ /* One line per collection (JS_GC_TRACE=1), on stderr. Its format is for people and scripts: fields
1783
+ * are added at the end. */
1784
+ static void trace_collection(const JSHeap *h, bool full, double pause, double mark, double weak, double finish, size_t heap_before,
1785
+ unsigned slices, double marking_ms) {
1786
+ const char *kind = !full ? "young" : h->idle ? "idle" : "full";
1787
+ char incremental[96] = ""; /* the final pause of an incremental marking: its slices before it */
1788
+ if (slices) snprintf(incremental, sizeof incremental, ", incremental: %u slices, %.3f ms", slices, marking_ms);
1789
+ fprintf(stderr, "[gc] #%zu %s: pause %.3f ms (mark %.3f, weak %.3f, finish %.3f), heap %zu KiB -> live %zu KiB, %llu cells marked, external %zu KiB, old limit %zu KiB%s\n",
1790
+ h->gc_count, kind, pause, mark, weak, finish, heap_before >> 10, h->live_after_gc >> 10, (unsigned long long)h->stats.marked_cells,
1791
+ h->external_bytes >> 10, h->old_limit >> 10, incremental);
1792
+ }
1793
+
1794
+ /* ---- Policy ------------------------------------------------------------ */
1795
+
1796
+ /* Full collections should take about this share of the program's CPU time. */
1797
+ #define GC_TARGET 0.05
1798
+ /* The old generation's growth between full collections, as a factor of its size after one. */
1799
+ #define GROWTH_MIN 1.25
1800
+ #define GROWTH_MAX 2.0
1801
+
1802
+ /* The old generation's size: its cells, and the external memory. The external memory of young
1803
+ * cells is in it too, which a young collection frees or makes old. */
1804
+ static size_t old_size(const JSHeap *h) { return h->live_after_gc + h->external_bytes; }
1805
+
1806
+ /* The program's growth rate of the old generation, in bytes per ms of its CPU time, since the
1807
+ * last full collection; read when a full collection starts. -1 when it ran too briefly to tell. */
1808
+ static double promotion_rate(const JSHeap *h) {
1809
+ double mutator = js_os_thread_cpu_ms() - h->full_end_cpu - h->pause_since_full;
1810
+ size_t now = old_size(h);
1811
+ if (mutator < 1) return -1;
1812
+ return now > h->old_after_full ? (double)(now - h->old_after_full) / mutator : 0;
1813
+ }
1814
+
1815
+ /* After a full collection that took `cost` ms: how big the old generation may grow before the
1816
+ * next one. Growing at `rate`, by rate × cost / GC_TARGET bytes, the next full collection comes
1817
+ * after cost / GC_TARGET ms of the program's time: full collections take GC_TARGET of it (Go's
1818
+ * and V8's heap controllers aim at a share of time too). Bounded by GROWTH_MIN and GROWTH_MAX,
1819
+ * and never below JS_GC_MIN_HEAP; JS_GC_GROWTH fixes the factor instead. */
1820
+ static size_t next_old_limit(const JSHeap *h, double rate, double cost) {
1821
+ double old = (double)h->old_after_full, grow;
1822
+ if (h->growth > 0) grow = (h->growth - 1) * old;
1823
+ else if (rate < 0) grow = (GROWTH_MAX - 1) * old;
1824
+ else {
1825
+ grow = rate * cost / GC_TARGET;
1826
+ if (grow < (GROWTH_MIN - 1) * old) grow = (GROWTH_MIN - 1) * old;
1827
+ if (grow > (GROWTH_MAX - 1) * old) grow = (GROWTH_MAX - 1) * old;
1828
+ }
1829
+ size_t limit = h->old_after_full + (size_t)grow;
1830
+ return limit > h->min_heap ? limit : h->min_heap;
1831
+ }
1832
+
1833
+ /* A full collection's start: the sweeping finished (a full collection clears the mark bits, and an
1834
+ * unswept block's dead cells must not come back), every mark bit cleared, the remembered set
1835
+ * forgotten (everything is marked). */
1836
+ static void begin_full(JSRuntime *rt) {
1837
+ JSHeap *h = &rt->heap;
1838
+ finish_sweeping(rt);
1839
+ for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++)
1840
+ for (JSBlock *b = h->blocks[sc]; b; b = b->next) memset(b->mark_bits, 0, sizeof b->mark_bits);
1841
+ for (size_t i = 0; i < h->large_count; i++) h->large[i]->mark_bits[0] = 0;
1842
+ forget_remembered(h);
1843
+ }
1844
+
1845
+ /* The allocation so far, cells and external memory, since the last collection: incremental
1846
+ * marking's clock. */
1847
+ static size_t allocation_clock(const JSHeap *h) { return h->allocated_since_gc + h->external_since_gc; }
1848
+
1849
+ /* Counts what was allocated since the last collection as done with: the next one counts anew. */
1850
+ static void reset_allocation_clock(JSHeap *h) {
1851
+ h->stats.allocated_bytes += h->allocated_since_gc;
1852
+ h->allocated_since_idle += h->allocated_since_gc + h->external_since_gc;
1853
+ h->allocated_since_gc = 0;
1854
+ h->external_since_gc = 0;
1855
+ h->collect_requested = 0;
1856
+ }
1857
+
1858
+ static void count_pause(JSHeap *h, double pause) {
1859
+ JSGCStats *st = &h->stats;
1860
+ st->pause_ms += pause;
1861
+ if (pause > st->max_pause_ms) st->max_pause_ms = pause;
1862
+ }
1863
+
1864
+ /* A full collection marks everything; a young one keeps the old cells' mark bits, and marks
1865
+ * from the roots, the stack and the remembered set what's new. With an incremental marking in
1866
+ * progress, any collection is its final pause: the marker has visited what it found while the
1867
+ * program ran; the roots and the stack, which no barrier watches, are marked again, and what
1868
+ * they reach. */
1869
+ static JS_NOINLINE void collect(JSContext *ctx, const void *stack_low, bool full) {
671
1870
  JSRuntime *rt = ctx->rt;
672
1871
  JSHeap *h = &rt->heap;
673
1872
  if (h->in_gc) js_fatal("js_gc called during a collection (from a finalizer?)");
674
1873
  h->in_gc = 1;
1874
+ double start = js_os_now_ms_precise();
1875
+ size_t heap_before = js_heap_size(rt);
1876
+ JSTracer *marker = h->marker;
1877
+ if (marker && h->exact) { /* js_gc, the heap limit: a marking from scratch, without what the one in progress kept */
1878
+ h->mark_stack = marker->stack;
1879
+ h->mark_stack_cap = marker->cap;
1880
+ free(marker);
1881
+ h->marker = marker = NULL;
1882
+ h->marking_ms = 0;
1883
+ h->marking_slices = 0;
1884
+ }
1885
+ h->exact = false;
1886
+ if (marker) full = true; /* no young collection during a marking: it cleared the old cells' marks */
1887
+ double rate = marker ? h->mark_rate : full ? promotion_rate(h) : 0;
675
1888
  stop_allocation(h);
676
- finish_sweeping(rt);
677
1889
 
678
- for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++)
679
- for (JSBlock *b = h->blocks[sc]; b; b = b->next) memset(b->mark_bits, 0, sizeof b->mark_bits);
680
- for (size_t i = 0; i < h->large_count; i++) h->large[i]->mark_bits[0] = 0;
1890
+ JSTracer local, *trc = marker;
1891
+ if (marker) {
1892
+ trc->stack_low = stack_low;
1893
+ drain_all(trc); /* what the slices left */
1894
+ #ifdef JS_GC_VERIFY
1895
+ verify_marking(h, trc);
1896
+ #endif
1897
+ } else {
1898
+ local = (JSTracer){ .heap = h, .stack = h->mark_stack, .cap = h->mark_stack_cap, .stack_low = stack_low };
1899
+ trc = &local;
1900
+ if (full) {
1901
+ begin_full(rt);
1902
+ } else { /* a young collection reads a queued block's marks: no sweeping first */
1903
+ #ifdef JS_GC_VERIFY
1904
+ verify_remembered(h, trc);
1905
+ #endif
1906
+ mark_remembered(h, trc);
1907
+ forget_remembered(h);
1908
+ }
1909
+ }
1910
+ mark_roots(rt, trc);
1911
+ mark_stack(trc);
1912
+ drain(trc);
1913
+ while (js_weak_ephemerons(rt, trc)) drain(trc); /* a WeakMap value lives if its key does */
1914
+ h->mark_stack = trc->stack; /* kept for the next collection */
1915
+ h->mark_stack_cap = trc->cap;
1916
+ double marked = js_os_now_ms_precise();
681
1917
 
682
- JSTracer trc = { h, 0, stack_low };
683
- mark_roots(rt, &trc);
684
- mark_stack(&trc);
685
- drain(&trc);
686
- while (js_weak_ephemerons(rt, &trc)) drain(&trc); /* a WeakMap value lives if its key does */
687
1918
  js_weak_sweep(rt);
688
1919
  js_atoms_sweep(rt); /* weak references, before anything is freed */
689
1920
  js_shapes_sweep(rt);
690
1921
  js_prop_caches_sweep(rt);
1922
+ double weak = js_os_now_ms_precise();
691
1923
 
692
- finalize_dead(rt);
1924
+ finalize_dead(rt, full);
693
1925
  sweep_large(rt);
694
- for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++) {
695
- for (JSBlock *b = h->blocks[sc]; b; b = b->next) b->unswept = 1;
696
- h->sweep_link[sc] = &h->blocks[sc];
1926
+ if (full) {
1927
+ for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++) {
1928
+ for (JSBlock *b = h->blocks[sc]; b; b = b->next) queue_sweep(h, b);
1929
+ h->ready[sc].count = 0; /* queued: swept again before they're used */
1930
+ }
697
1931
  }
698
- h->live_after_gc = marked_bytes(h);
699
- h->allocated_since_gc = 0;
1932
+ for (size_t i = 0; i < h->young_blocks.count; i++) {
1933
+ JSBlock *b = h->young_blocks.items[i];
1934
+ b->young = 0;
1935
+ if (!full) queue_sweep(h, b);
1936
+ }
1937
+ h->young_blocks.count = 0;
700
1938
  h->gc_count++;
1939
+ /* the old generation: a young collection adds its survivors, a full one counts them all; the
1940
+ * finalizers have freed the dead cells' external memory */
1941
+ h->live_after_gc = full ? trc->marked_bytes : h->live_after_gc + trc->marked_bytes;
1942
+ size_t marked_cells = trc->marked;
1943
+ reset_allocation_clock(h);
1944
+
1945
+ double end = js_os_now_ms_precise(), pause = end - start;
1946
+ double incremental_ms = marker ? h->marking_ms : 0;
1947
+ unsigned slices = marker ? h->marking_slices : 0;
1948
+ if (marker) {
1949
+ free(marker);
1950
+ h->marker = NULL;
1951
+ h->marking_ms = 0;
1952
+ h->marking_slices = 0;
1953
+ }
1954
+ if (full) {
1955
+ h->full_count++;
1956
+ unmap_unused_blocks(h);
1957
+ h->old_after_full = old_size(h);
1958
+ h->old_limit = next_old_limit(h, rate, pause + incremental_ms); /* the marking's whole cost */
1959
+ h->full_end_cpu = js_os_thread_cpu_ms();
1960
+ h->pause_since_full = 0;
1961
+ } else {
1962
+ h->pause_since_full += pause;
1963
+ }
1964
+ h->full_next = old_size(h) > h->old_limit; /* the old generation outgrew its limit */
1965
+ h->in_gc = 0;
1966
+
1967
+ JSGCStats *st = &h->stats;
1968
+ st->collections++;
1969
+ if (!full) st->young_collections++;
1970
+ if (h->idle) st->idle_collections++;
1971
+ count_pause(h, pause);
1972
+ st->mark_ms += marked - start;
1973
+ st->weak_ms += weak - marked;
1974
+ st->finish_ms += end - weak;
1975
+ st->marked_cells = marked_cells;
1976
+ if (h->report & 1) trace_collection(h, full, pause, marked - start, weak - marked, end - weak, heap_before, slices, incremental_ms);
1977
+ }
1978
+
1979
+ /* Starts an old collection's marking, in a short pause: the
1980
+ * sweeping finished, the mark bits cleared, the precise roots marked. Then the program runs, and
1981
+ * allocating mark_ratio bytes for a byte marks a slice every SLICE_BYTES, so the marking is done
1982
+ * when the program has allocated about an eighth of the old generation (at least two
1983
+ * nurseries): there are no young collections meanwhile, and that's what the heap grows by. */
1984
+ #define SLICE_BYTES ((size_t)256 << 10)
1985
+ /* A slice marks at most this much, about a millisecond: one large allocation (an 8 MB array's new
1986
+ * elements) would otherwise owe the whole marking at once. What's owed is marked by the next
1987
+ * slices, or the final pause. */
1988
+ #define MAX_SLICE ((size_t)4 << 20)
1989
+
1990
+ static JS_NOINLINE void start_marking_at(JSContext *ctx, const void *stack_low) {
1991
+ JSRuntime *rt = ctx->rt;
1992
+ JSHeap *h = &rt->heap;
1993
+ h->in_gc = 1;
1994
+ double start = js_os_now_ms_precise();
1995
+ h->mark_rate = promotion_rate(h);
1996
+ JSBlock *current[JS_NUM_SIZE_CLASSES];
1997
+ for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++) current[sc] = h->alloc[sc].block;
1998
+ stop_allocation(h); /* exact allocation bits, for the stack scan */
1999
+ begin_full(rt);
2000
+ for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++) /* the rest of the blocks they were in, for the marking's allocation */
2001
+ if (current[sc]) list_push(&h->ready[sc], current[sc]);
2002
+ JSTracer *trc = xrealloc(NULL, sizeof *trc);
2003
+ *trc = (JSTracer){ .heap = h, .stack = h->mark_stack, .cap = h->mark_stack_cap, .stack_low = stack_low };
2004
+ h->mark_stack = NULL; /* the marker's until the final pause */
2005
+ h->mark_stack_cap = 0;
2006
+ mark_roots(rt, trc);
2007
+ mark_stack(trc); /* the final pause scans it again: this is a head start (a program's data is often only there) */
2008
+ flush_fifo(trc);
2009
+ h->marker = trc;
2010
+
2011
+ size_t work = h->live_after_gc > h->min_heap ? h->live_after_gc : h->min_heap; /* about what's to mark */
2012
+ size_t during = work / 8 > 2 * h->nursery ? work / 8 : 2 * h->nursery;
2013
+ h->mark_ratio = 1.25 * (double)work / (double)during; /* some headroom: what the barrier marks too */
2014
+ h->mark_alloc_limit = 2 * during;
2015
+ reset_allocation_clock(h);
2016
+ h->full_next = 0;
2017
+ h->slice_clock = 0;
2018
+ h->next_slice = SLICE_BYTES;
2019
+ h->in_gc = 0;
2020
+
2021
+ double pause = js_os_now_ms_precise() - start;
2022
+ h->marking_ms = pause;
2023
+ h->marking_slices = 1;
2024
+ count_pause(h, pause);
2025
+ h->stats.mark_ms += pause;
2026
+ h->stats.mark_slices++;
2027
+ }
2028
+
2029
+ static JS_NOINLINE void start_marking(JSContext *ctx) {
2030
+ jmp_buf regs; /* the mutator's registers, for the stack scan: as collect_spilled */
2031
+ memset(&regs, 0, sizeof regs);
2032
+ #if defined(__clang__) || defined(__GNUC__)
2033
+ __builtin_unwind_init();
2034
+ #endif
2035
+ setjmp(regs);
2036
+ start_marking_at(ctx, &regs);
2037
+ (void)*(volatile char *)&regs;
2038
+ }
2039
+
2040
+ /* A slice of `budget` bytes, or of what the allocation since the last one calls for (0). When the
2041
+ * marker has nothing left, or the program allocated as much as the marking may take, the final
2042
+ * pause comes next. */
2043
+ static void mark_slice(JSContext *ctx, size_t budget) {
2044
+ JSHeap *h = &ctx->rt->heap;
2045
+ h->in_gc = 1;
2046
+ h->collect_requested = 0;
2047
+ double start = js_os_now_ms_precise();
2048
+ size_t clock = allocation_clock(h);
2049
+ if (budget) {
2050
+ h->slice_clock = clock;
2051
+ } else { /* what the allocation since the last slice calls for, up to MAX_SLICE: the rest is owed */
2052
+ budget = (size_t)((double)(clock - h->slice_clock) * h->mark_ratio);
2053
+ if (budget > MAX_SLICE) budget = MAX_SLICE;
2054
+ h->slice_clock += (size_t)((double)budget / h->mark_ratio);
2055
+ if (h->slice_clock > clock) h->slice_clock = clock;
2056
+ }
2057
+ h->next_slice = clock + SLICE_BYTES;
2058
+ bool done = drain_some(h->marker, budget);
701
2059
  h->in_gc = 0;
2060
+
2061
+ double pause = js_os_now_ms_precise() - start;
2062
+ h->marking_ms += pause;
2063
+ h->marking_slices++;
2064
+ count_pause(h, pause);
2065
+ h->stats.mark_ms += pause;
2066
+ h->stats.mark_slices++;
2067
+ if (done || clock >= h->mark_alloc_limit) collect_spilled(ctx, true);
702
2068
  }
703
2069
 
704
2070
  /* Spills the mutator's callee-saved registers into this frame, then
705
2071
  * collects in deeper frames that the stack scan doesn't cover. */
706
- JS_NOINLINE void js_gc(JSContext *ctx) {
2072
+ static JS_NOINLINE void collect_spilled(JSContext *ctx, bool full) {
707
2073
  jmp_buf regs;
708
2074
  memset(&regs, 0, sizeof regs); /* no stale words in the parts setjmp skips */
709
2075
  #if defined(__clang__) || defined(__GNUC__)
710
2076
  __builtin_unwind_init();
711
2077
  #endif
712
2078
  setjmp(regs);
713
- collect(ctx, &regs);
2079
+ collect(ctx, &regs, full);
714
2080
  (void)*(volatile char *)&regs; /* keep this frame alive: no tail call */
715
2081
  }
716
2082
 
2083
+ JS_NOINLINE void js_gc(JSContext *ctx) {
2084
+ ctx->rt->heap.exact = true; /* everything unreachable now: a marking in progress keeps some */
2085
+ collect_spilled(ctx, true);
2086
+ }
2087
+ JS_NOINLINE void js_gc_young(JSContext *ctx) { collect_spilled(ctx, false); }
2088
+
2089
+ /* Worth it when the program allocated, since the last idle collection, as much as a young
2090
+ * collection's worth, and a quarter of the old generation: what a full collection would find
2091
+ * free, and what the program would grow by before the next one anyway. */
2092
+ bool js_gc_idle_wanted(const JSRuntime *rt) {
2093
+ const JSHeap *h = &rt->heap;
2094
+ if (h->marker) return true; /* a marking in progress is finished while nothing else runs */
2095
+ size_t allocated = h->allocated_since_idle + h->allocated_since_gc + h->external_since_gc;
2096
+ size_t enough = old_size(h) / 4 > h->nursery ? old_size(h) / 4 : h->nursery;
2097
+ return allocated >= enough;
2098
+ }
2099
+
2100
+ void js_gc_idle(JSContext *ctx) {
2101
+ JSRuntime *rt = ctx->rt;
2102
+ JSHeap *h = &rt->heap;
2103
+ h->idle = true;
2104
+ collect_spilled(ctx, true);
2105
+ h->idle = false;
2106
+ finish_sweeping(rt); /* the empty blocks go to the pool, or back to the OS */
2107
+ while (h->empty) { /* and the pool's too: the program is waiting */
2108
+ JSBlock *b = h->empty;
2109
+ h->empty = b->next;
2110
+ h->empty_count--;
2111
+ decommit_block(h, b);
2112
+ }
2113
+ unmap_large_cache(h);
2114
+ js_os_trim(); /* what the finalizers freed: buffers' bytes */
2115
+ h->allocated_since_idle = 0;
2116
+ }
2117
+
2118
+ /* Below this much old generation, a full collection's pause is about a millisecond: it isn't
2119
+ * worth marking incrementally (no young collections meanwhile, and the barrier's slow path). */
2120
+ #define INCREMENTAL_MIN ((size_t)8 << 20)
2121
+
2122
+ /* The collection the policy asks for: young, or full when the old generation has outgrown its
2123
+ * limit, which starts an incremental marking when it's large enough; full at once when the heap
2124
+ * is near its limit (and it finishes a marking in progress). */
2125
+ static void collect_as_needed(JSContext *ctx, bool near_limit) {
2126
+ JSHeap *h = &ctx->rt->heap;
2127
+ if (h->full_next && !near_limit && !h->marker && h->incremental && h->live_after_gc >= INCREMENTAL_MIN) {
2128
+ start_marking(ctx);
2129
+ return;
2130
+ }
2131
+ if (near_limit) h->exact = true;
2132
+ collect_spilled(ctx, near_limit || h->full_next || h->marker);
2133
+ }
2134
+
2135
+ /* What allocation's slow paths owe the collector, when it's anything: a collection the external
2136
+ * memory asked for, or a marking slice. */
2137
+ static JS_NOINLINE void gc_poll(JSContext *ctx) {
2138
+ JSHeap *h = &ctx->rt->heap;
2139
+ if (h->marker) {
2140
+ if (h->collect_requested || allocation_clock(h) >= h->next_slice) mark_slice(ctx, 0);
2141
+ } else if (h->collect_requested) {
2142
+ collect_as_needed(ctx, false);
2143
+ }
2144
+ }
2145
+
2146
+ void js_gc_start_marking(JSContext *ctx) {
2147
+ if (!ctx->rt->heap.marker) start_marking(ctx);
2148
+ }
2149
+
2150
+ bool js_gc_marking(const JSRuntime *rt) { return rt->heap.marker != NULL; }
2151
+
2152
+ void js_gc_mark_slice(JSContext *ctx, size_t bytes) {
2153
+ if (ctx->rt->heap.marker) mark_slice(ctx, bytes ? bytes : 1);
2154
+ }
2155
+
717
2156
  /* ---- Runtime lifetime -------------------------------------------------- */
718
2157
 
2158
+ /* JS_GC_STATS=1: one JSON line on stderr when the runtime ends, or when the process exits first
2159
+ * (process.exit). Prefixed `[gc-stats] ` so tools can find it among the program's output. */
2160
+ static JSRuntime *stats_runtime; /* the runtime whose statistics the exit handler prints */
2161
+
2162
+ static void print_stats(const JSRuntime *rt) {
2163
+ const JSGCStats *s = &rt->heap.stats;
2164
+ fprintf(stderr,
2165
+ "[gc-stats] {\"collections\":%llu,\"young_collections\":%llu,\"pause_ms\":%.3f,\"max_pause_ms\":%.3f,\"mark_ms\":%.3f,"
2166
+ "\"weak_ms\":%.3f,\"finish_ms\":%.3f,\"sweep_ms\":%.3f,\"blocks_swept\":%llu,"
2167
+ "\"allocated_bytes\":%llu,\"peak_heap_bytes\":%zu,\"peak_rss_bytes\":%zu,\"peak_external_bytes\":%zu,\"idle_collections\":%llu,\"mark_slices\":%llu,"
2168
+ "\"parallel_markings\":%llu,\"gc_threads\":%u}\n",
2169
+ (unsigned long long)s->collections, (unsigned long long)s->young_collections, s->pause_ms, s->max_pause_ms, s->mark_ms, s->weak_ms, s->finish_ms,
2170
+ s->sweep_ms, (unsigned long long)s->blocks_swept,
2171
+ (unsigned long long)(s->allocated_bytes + rt->heap.allocated_since_gc), s->peak_heap_bytes, js_os_peak_rss(), s->peak_external_bytes, (unsigned long long)s->idle_collections, (unsigned long long)s->mark_slices,
2172
+ (unsigned long long)s->parallel_markings, rt->heap.pool ? rt->heap.pool->helpers : 0);
2173
+ }
2174
+
2175
+ static void print_stats_at_exit(void) {
2176
+ if (stats_runtime) print_stats(stats_runtime);
2177
+ }
2178
+
2179
+ static int report_flags(void) {
2180
+ const char *trace = getenv("JS_GC_TRACE"), *stats = getenv("JS_GC_STATS");
2181
+ return (trace && *trace && *trace != '0' ? 1 : 0) | (stats && *stats && *stats != '0' ? 2 : 0);
2182
+ }
2183
+
2184
+ const JSGCStats *js_gc_stats(const JSRuntime *rt) {
2185
+ return &rt->heap.stats;
2186
+ }
2187
+
719
2188
  JSRuntime *js_runtime_new(void) {
720
2189
  JSRuntime *rt = calloc(1, sizeof *rt);
721
2190
  if (!rt) js_fatal("out of memory creating a runtime");
722
- for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++) rt->heap.sweep_link[sc] = &rt->heap.blocks[sc];
2191
+ for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++) rt->heap.alloc[sc].cell_size = js_size_class_bytes[sc];
723
2192
  void *stack_low;
724
2193
  js_os_stack_bounds(&stack_low, &rt->heap.stack_base);
725
2194
  rt->stack_limit = (uintptr_t)stack_low + JS_STACK_MARGIN;
@@ -727,10 +2196,32 @@ JSRuntime *js_runtime_new(void) {
727
2196
  rt->symbol_registry = JS_UNDEFINED;
728
2197
  rt->async_context = JS_UNDEFINED;
729
2198
  rt->heap.min_heap = JS_GC_MIN_HEAP;
2199
+ rt->heap.nursery = JS_GC_NURSERY;
2200
+ rt->heap.old_limit = JS_GC_MIN_HEAP;
2201
+ /* helper threads for the pauses' marking: two, or one less than the processors (env
2202
+ * JS_GC_THREADS: how many; 0 marks alone). A third was measured: no shorter pauses. */
2203
+ unsigned cpus = js_os_cpu_count();
2204
+ rt->heap.gc_helpers = cpus > 3 ? 2 : cpus - 1;
2205
+ const char *threads = getenv("JS_GC_THREADS");
2206
+ if (threads && *threads) rt->heap.gc_helpers = (unsigned)strtoul(threads, NULL, 10);
2207
+ if (rt->heap.gc_helpers > MAX_HELPERS) rt->heap.gc_helpers = MAX_HELPERS;
2208
+ const char *incremental = getenv("JS_GC_INCREMENTAL"); /* 0: old collections stop the world */
2209
+ rt->heap.incremental = !(incremental && *incremental == '0');
2210
+ const char *growth = getenv("JS_GC_GROWTH"); /* a fixed growth factor, for experiments */
2211
+ if (growth && strtod(growth, NULL) >= 1) rt->heap.growth = strtod(growth, NULL);
2212
+ const char *nursery = getenv("JS_GC_NURSERY_MB"); /* for tuning experiments */
2213
+ if (nursery && strtoull(nursery, NULL, 10)) rt->heap.nursery = (size_t)strtoull(nursery, NULL, 10) << 20;
730
2214
  const char *limit = getenv("JS_HEAP_LIMIT_MB");
731
2215
  if (limit) rt->heap.heap_limit = (size_t)strtoull(limit, NULL, 10) << 20;
732
2216
  const char *interval = getenv("JS_GC_STRESS_INTERVAL");
733
2217
  rt->heap.stress_interval = interval ? (size_t)strtoull(interval, NULL, 10) : 1;
2218
+ rt->heap.report = report_flags();
2219
+ if (rt->heap.report & 2) {
2220
+ static int registered;
2221
+ if (!registered) atexit(print_stats_at_exit);
2222
+ registered = 1;
2223
+ stats_runtime = rt;
2224
+ }
734
2225
  js_atoms_init(rt);
735
2226
  return rt;
736
2227
  }
@@ -740,7 +2231,7 @@ void js_set_heap_limit(JSRuntime *rt, size_t bytes) {
740
2231
  }
741
2232
 
742
2233
  size_t js_heap_size(const JSRuntime *rt) {
743
- return rt->heap.block_count * JS_BLOCK_SIZE + rt->heap.large_bytes;
2234
+ return (rt->heap.block_count - rt->heap.decommitted_count) * JS_BLOCK_SIZE + rt->heap.large_bytes;
744
2235
  }
745
2236
 
746
2237
  /* Runs the finalizers of every allocated cell in `b` that has one. */
@@ -754,6 +2245,8 @@ static void finalize_block(JSRuntime *rt, JSBlock *b) {
754
2245
 
755
2246
  void js_runtime_free(JSRuntime *rt) {
756
2247
  JSHeap *h = &rt->heap;
2248
+ if (h->report & 2) print_stats(rt);
2249
+ if (stats_runtime == rt) stats_runtime = NULL;
757
2250
  stop_allocation(h);
758
2251
  /* every finalizer first, then the memory: a finalizer may read cells in other blocks (shapes) */
759
2252
  for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++)
@@ -770,6 +2263,8 @@ void js_runtime_free(JSRuntime *rt) {
770
2263
  js_os_unmap(b, JS_BLOCK_SIZE);
771
2264
  }
772
2265
  for (size_t i = 0; i < h->large_count; i++) js_os_unmap(h->large[i], h->large[i]->map_size);
2266
+ for (size_t i = 0; i < h->decommitted_count; i++) js_os_unmap(h->decommitted[i].block, JS_BLOCK_SIZE);
2267
+ unmap_large_cache(h);
773
2268
  js_atoms_free(rt);
774
2269
  js_shapes_free(rt);
775
2270
  js_prop_caches_free(rt);
@@ -780,7 +2275,20 @@ void js_runtime_free(JSRuntime *rt) {
780
2275
  free(h->block_set);
781
2276
  free(h->root_tables);
782
2277
  free(h->roots);
2278
+ free_mark_pool(h);
783
2279
  free(h->mark_stack);
2280
+ if (h->marker) {
2281
+ free(h->marker->stack);
2282
+ free(h->marker);
2283
+ }
2284
+ free(h->remembered);
2285
+ free(h->decommitted);
2286
+ free(h->large_cache);
2287
+ for (unsigned sc = 0; sc < JS_NUM_SIZE_CLASSES; sc++) {
2288
+ free(h->to_sweep[sc].items);
2289
+ free(h->ready[sc].items);
2290
+ }
2291
+ free(h->young_blocks.items);
784
2292
  free(rt);
785
2293
  }
786
2294
 
@@ -789,11 +2297,12 @@ JSContext *js_context_new(JSRuntime *rt) {
789
2297
  if (!ctx) js_fatal("out of memory creating a context");
790
2298
  ctx->rt = rt;
791
2299
  ctx->stack_limit = rt->stack_limit;
2300
+ ctx->barrier_threshold = JS_GC_OLD;
792
2301
  for (unsigned i = 0; i < JS_INTRINSIC_COUNT; i++) ctx->intrinsics[i] = JS_UNDEFINED;
793
2302
  ctx->next_realm = rt->realms;
794
2303
  rt->realms = ctx;
795
2304
  js_realm_init(ctx);
796
- js_install_recorded_components(ctx); /* the program's optional built-ins (ADR 0014) */
2305
+ js_install_recorded_components(ctx); /* the program's optional built-ins */
797
2306
  return ctx;
798
2307
  }
799
2308