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.
- package/README.md +63 -2
- package/THIRD_PARTY_LICENSES +1216 -0
- package/cli.js +4414 -325
- package/node/include/js_node.h +57 -1
- package/node/include/js_uv.h +8 -1
- package/node/prelude/_stream_duplex.js +4 -0
- package/node/prelude/_stream_passthrough.js +4 -0
- package/node/prelude/_stream_readable.js +4 -0
- package/node/prelude/_stream_transform.js +4 -0
- package/node/prelude/_stream_writable.js +4 -0
- package/node/prelude/assert.js +1334 -0
- package/node/prelude/assert_strict.js +12 -0
- package/node/prelude/async_hooks.js +1 -1
- package/node/prelude/buffer.js +43 -3
- package/node/prelude/child_process.js +1098 -0
- package/node/prelude/cluster.js +52 -0
- package/node/prelude/console.js +328 -0
- package/node/prelude/constants.js +10 -0
- package/node/prelude/crypto.js +1 -1
- package/node/prelude/diagnostics_channel.js +1 -1
- package/node/prelude/dns.js +1 -1
- package/node/prelude/domain.js +115 -0
- package/node/prelude/events.js +144 -16
- package/node/prelude/fs.js +1138 -151
- package/node/prelude/fs_promises.js +117 -11
- package/node/prelude/http.js +104 -27
- package/node/prelude/http2.js +1 -1
- package/node/prelude/https.js +148 -0
- package/node/prelude/inspector.js +27 -0
- package/node/prelude/inspector_promises.js +4 -0
- package/node/prelude/internal/async_context.js +1 -1
- package/node/prelude/internal/buffer_transcode.js +44 -0
- package/node/prelude/internal/comparisons.js +916 -0
- package/node/prelude/internal/compression_streams.js +153 -0
- package/node/prelude/internal/crypto/asym.js +27 -14
- package/node/prelude/internal/crypto/cipher.js +16 -5
- package/node/prelude/internal/crypto/diffiehellman.js +3 -2
- package/node/prelude/internal/crypto/hash.js +2 -1
- package/node/prelude/internal/crypto/kdf.js +14 -9
- package/node/prelude/internal/crypto/keys.js +63 -50
- package/node/prelude/internal/crypto/random.js +2 -1
- package/node/prelude/internal/crypto/util.js +4 -4
- package/node/prelude/internal/dotenv.js +98 -0
- package/node/prelude/internal/errors.js +21 -5
- package/node/prelude/internal/events_extras.js +116 -0
- package/node/prelude/internal/events_on.js +100 -0
- package/node/prelude/internal/features.js +55 -0
- package/node/prelude/internal/fetch.js +364 -0
- package/node/prelude/internal/form_data.js +376 -0
- package/node/prelude/internal/fs_bigint_stats.js +41 -0
- package/node/prelude/internal/fs_blob.js +19 -0
- package/node/prelude/internal/fs_cp.js +709 -0
- package/node/prelude/internal/fs_dir.js +166 -0
- package/node/prelude/internal/fs_filehandle.js +287 -0
- package/node/prelude/internal/fs_glob.js +296 -0
- package/node/prelude/internal/fs_statfs.js +42 -0
- package/node/prelude/internal/fs_stats.js +89 -0
- package/node/prelude/internal/fs_streams.js +541 -0
- package/node/prelude/internal/fs_utils.js +286 -0
- package/node/prelude/internal/fs_watch.js +580 -0
- package/node/prelude/internal/http_agent.js +26 -0
- package/node/prelude/internal/mime.js +249 -0
- package/node/prelude/internal/net.js +1 -0
- package/node/prelude/internal/parse_args.js +288 -0
- package/node/prelude/internal/perf_histogram.js +435 -0
- package/node/prelude/internal/perf_observe.js +119 -0
- package/node/prelude/internal/performance_global.js +19 -0
- package/node/prelude/internal/process_events.js +49 -1
- package/node/prelude/internal/process_memory.js +14 -0
- package/node/prelude/internal/process_more.js +414 -0
- package/node/prelude/internal/process_signals.js +2 -1
- package/node/prelude/internal/process_stdin.js +52 -0
- package/node/prelude/internal/process_stdio.js +36 -0
- package/node/prelude/internal/querystring.js +0 -0
- package/node/prelude/internal/readline.js +338 -0
- package/node/prelude/internal/socketaddress.js +8 -4
- package/node/prelude/internal/source_map.js +122 -0
- package/node/prelude/internal/stream_compose.js +74 -0
- package/node/prelude/internal/stream_from.js +6 -0
- package/node/prelude/internal/stream_operators.js +23 -0
- package/node/prelude/internal/stream_pipeline.js +7 -0
- package/node/prelude/internal/stream_transform.js +10 -0
- package/node/prelude/internal/stream_web_adapters.js +143 -0
- package/node/prelude/internal/streams/add_abort_signal.js +58 -0
- package/node/prelude/internal/streams/compose.js +244 -0
- package/node/prelude/internal/streams/destroy.js +358 -0
- package/node/prelude/internal/streams/duplex.js +199 -0
- package/node/prelude/internal/streams/duplexify.js +378 -0
- package/node/prelude/internal/streams/end_of_stream.js +387 -0
- package/node/prelude/internal/streams/errors.js +216 -0
- package/node/prelude/internal/streams/from.js +211 -0
- package/node/prelude/internal/streams/legacy.js +120 -0
- package/node/prelude/internal/streams/operators.js +375 -0
- package/node/prelude/internal/streams/passthrough.js +45 -0
- package/node/prelude/internal/streams/pipeline.js +468 -0
- package/node/prelude/internal/streams/readable.js +1835 -0
- package/node/prelude/internal/streams/state.js +48 -0
- package/node/prelude/internal/streams/transform.js +203 -0
- package/node/prelude/internal/streams/utils.js +355 -0
- package/node/prelude/internal/streams/validators.js +108 -0
- package/node/prelude/internal/streams/writable.js +1160 -0
- package/node/prelude/internal/structured_clone.js +238 -0
- package/node/prelude/internal/system_errors.js +37 -0
- package/node/prelude/internal/test_runner/mock.js +431 -0
- package/node/prelude/internal/test_runner/reporters.js +404 -0
- package/node/prelude/internal/test_runner/test.js +1571 -0
- package/node/prelude/internal/timers.js +38 -7
- package/node/prelude/internal/timers_global.js +1 -1
- package/node/prelude/internal/tls/common.js +8 -2
- package/node/prelude/internal/tls/wrap.js +3 -15
- package/node/prelude/internal/tls/x509.js +1 -1
- package/node/prelude/internal/tty_read_stream.js +39 -0
- package/node/prelude/internal/tty_streams.js +3 -0
- package/node/prelude/internal/util_debuglog.js +68 -0
- package/node/prelude/internal/util_diff.js +63 -0
- package/node/prelude/internal/util_more.js +111 -0
- package/node/prelude/internal/util_styles.js +76 -0
- package/node/prelude/internal/zlib_engines.js +1 -1
- package/node/prelude/module.js +194 -7
- package/node/prelude/net.js +2 -1
- package/node/prelude/os.js +88 -11
- package/node/prelude/path.js +11 -7
- package/node/prelude/path_posix.js +1 -1
- package/node/prelude/path_win32.js +1 -1
- package/node/prelude/perf_hooks.js +147 -0
- package/node/prelude/process.js +36 -1
- package/node/prelude/punycode.js +188 -0
- package/node/prelude/querystring.js +1 -1
- package/node/prelude/readline.js +8 -0
- package/node/prelude/readline_promises.js +88 -0
- package/node/prelude/repl.js +38 -0
- package/node/prelude/sea.js +23 -0
- package/node/prelude/stream.js +70 -1332
- package/node/prelude/stream_consumers.js +34 -0
- package/node/prelude/stream_promises.js +1 -1
- package/node/prelude/stream_web.js +15 -2
- package/node/prelude/string_decoder.js +1 -1
- package/node/prelude/sys.js +7 -0
- package/node/prelude/test.js +56 -0
- package/node/prelude/test_reporters.js +269 -0
- package/node/prelude/timers.js +7 -84
- package/node/prelude/timers_promises.js +1 -1
- package/node/prelude/tls.js +2 -1
- package/node/prelude/trace_events.js +52 -0
- package/node/prelude/tty.js +10 -4
- package/node/prelude/url.js +88 -27
- package/node/prelude/util.js +121 -5
- package/node/prelude/util_types.js +4 -3
- package/node/prelude/v8.js +814 -0
- package/node/prelude/vm.js +86 -0
- package/node/prelude/wasi.js +10 -0
- package/node/prelude/worker_threads.js +56 -0
- package/node/prelude/zlib.js +58 -25
- package/node/src/host_brotli.c +59 -11
- package/node/src/host_fs.c +342 -11
- package/node/src/host_fswatch.c +170 -0
- package/node/src/host_http.c +143 -20
- package/node/src/host_loop.c +25 -2
- package/node/src/host_net.c +55 -7
- package/node/src/host_os.c +55 -1
- package/node/src/host_proc.c +291 -0
- package/node/src/host_process.c +488 -0
- package/node/src/host_sys.c +64 -2
- package/node/src/host_zlib.c +27 -7
- package/node/src/host_zstd.c +41 -6
- package/node/third_party/hdrhistogram/LICENSE +42 -0
- package/node/third_party/hdrhistogram/README.md +5 -0
- package/node/third_party/libuv/LICENSE +0 -0
- package/node/third_party/libuv/LICENSE-extra +36 -0
- package/node/third_party/libuv/include/uv/aix.h +0 -0
- package/node/third_party/libuv/include/uv/bsd.h +0 -0
- package/node/third_party/libuv/include/uv/darwin.h +0 -0
- package/node/third_party/libuv/include/uv/errno.h +0 -0
- package/node/third_party/libuv/include/uv/linux.h +0 -0
- package/node/third_party/libuv/include/uv/os390.h +0 -0
- package/node/third_party/libuv/include/uv/posix.h +0 -0
- package/node/third_party/libuv/include/uv/sunos.h +0 -0
- package/node/third_party/libuv/include/uv/threadpool.h +0 -0
- package/node/third_party/libuv/include/uv/tree.h +0 -0
- package/node/third_party/libuv/include/uv/unix.h +0 -0
- package/node/third_party/libuv/include/uv/version.h +0 -0
- package/node/third_party/libuv/include/uv/win.h +0 -0
- package/node/third_party/libuv/include/uv.h +0 -0
- package/node/third_party/libuv/src/fs-poll.c +0 -0
- package/node/third_party/libuv/src/heap-inl.h +0 -0
- package/node/third_party/libuv/src/idna.c +0 -0
- package/node/third_party/libuv/src/idna.h +0 -0
- package/node/third_party/libuv/src/inet.c +0 -0
- package/node/third_party/libuv/src/queue.h +0 -0
- package/node/third_party/libuv/src/random.c +0 -0
- package/node/third_party/libuv/src/strscpy.c +0 -0
- package/node/third_party/libuv/src/strscpy.h +0 -0
- package/node/third_party/libuv/src/strtok.c +0 -0
- package/node/third_party/libuv/src/strtok.h +0 -0
- package/node/third_party/libuv/src/thread-common.c +0 -0
- package/node/third_party/libuv/src/threadpool.c +0 -0
- package/node/third_party/libuv/src/timer.c +0 -0
- package/node/third_party/libuv/src/unix/async.c +0 -0
- package/node/third_party/libuv/src/unix/bsd-ifaddrs.c +0 -0
- package/node/third_party/libuv/src/unix/core.c +0 -0
- package/node/third_party/libuv/src/unix/darwin-proctitle.c +0 -0
- package/node/third_party/libuv/src/unix/darwin-stub.h +0 -0
- package/node/third_party/libuv/src/unix/darwin-syscalls.h +0 -0
- package/node/third_party/libuv/src/unix/darwin.c +0 -0
- package/node/third_party/libuv/src/unix/dl.c +0 -0
- package/node/third_party/libuv/src/unix/fs.c +0 -0
- package/node/third_party/libuv/src/unix/fsevents.c +0 -0
- package/node/third_party/libuv/src/unix/getaddrinfo.c +0 -0
- package/node/third_party/libuv/src/unix/getnameinfo.c +0 -0
- package/node/third_party/libuv/src/unix/internal.h +0 -0
- package/node/third_party/libuv/src/unix/kqueue.c +0 -0
- package/node/third_party/libuv/src/unix/linux.c +0 -0
- package/node/third_party/libuv/src/unix/loop-watcher.c +0 -0
- package/node/third_party/libuv/src/unix/loop.c +0 -0
- package/node/third_party/libuv/src/unix/pipe.c +0 -0
- package/node/third_party/libuv/src/unix/poll.c +0 -0
- package/node/third_party/libuv/src/unix/process.c +0 -0
- package/node/third_party/libuv/src/unix/procfs-exepath.c +0 -0
- package/node/third_party/libuv/src/unix/proctitle.c +0 -0
- package/node/third_party/libuv/src/unix/random-devurandom.c +0 -0
- package/node/third_party/libuv/src/unix/random-getentropy.c +0 -0
- package/node/third_party/libuv/src/unix/random-getrandom.c +0 -0
- package/node/third_party/libuv/src/unix/random-sysctl-linux.c +0 -0
- package/node/third_party/libuv/src/unix/signal.c +0 -0
- package/node/third_party/libuv/src/unix/stream.c +0 -0
- package/node/third_party/libuv/src/unix/tcp.c +0 -0
- package/node/third_party/libuv/src/unix/thread.c +0 -0
- package/node/third_party/libuv/src/unix/tty.c +0 -0
- package/node/third_party/libuv/src/unix/udp.c +0 -0
- package/node/third_party/libuv/src/uv-common.c +0 -0
- package/node/third_party/libuv/src/uv-common.h +0 -0
- package/node/third_party/libuv/src/uv-data-getter-setters.c +0 -0
- package/node/third_party/libuv/src/version.c +0 -0
- package/node/third_party/llhttp/LICENSE +0 -0
- package/node/third_party/llhttp/api.c +0 -0
- package/node/third_party/llhttp/http.c +0 -0
- package/node/third_party/llhttp/llhttp.c +0 -0
- package/node/third_party/llhttp/llhttp.h +0 -0
- package/node/third_party/nodejs/LICENSE +47 -0
- package/node/third_party/nodejs/README.md +5 -0
- package/node/third_party/parseargs/LICENSE +21 -0
- package/node/third_party/parseargs/README.md +5 -0
- package/node/third_party/punycode/LICENSE +20 -0
- package/node/third_party/punycode/README.md +5 -0
- package/node/third_party/zlib/LICENSE.chromium +27 -0
- package/package.json +1 -5
- package/prebuilt/darwin-arm64/boringssl-4a47438eaf7e73ba.a +0 -0
- package/prebuilt/darwin-arm64/brotli-7995bbdacc52e51c.a +0 -0
- package/prebuilt/darwin-arm64/uv-72ab666eae197cd8.a +0 -0
- package/prebuilt/darwin-arm64/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/darwin-arm64/zstd-015315ac7c784c73.a +0 -0
- package/prebuilt/darwin-x64/boringssl-4a47438eaf7e73ba.a +0 -0
- package/prebuilt/darwin-x64/brotli-7995bbdacc52e51c.a +0 -0
- package/prebuilt/darwin-x64/uv-72ab666eae197cd8.a +0 -0
- package/prebuilt/darwin-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/darwin-x64/zstd-015315ac7c784c73.a +0 -0
- package/prebuilt/linux-arm64/boringssl-4a47438eaf7e73ba.a +0 -0
- package/prebuilt/linux-arm64/brotli-65481ff838ce9def.a +0 -0
- package/prebuilt/linux-arm64/uv-f1353f50ff983f3c.a +0 -0
- package/prebuilt/linux-arm64/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/linux-arm64/zstd-015315ac7c784c73.a +0 -0
- package/prebuilt/linux-arm64-musl/boringssl-4a47438eaf7e73ba.a +0 -0
- package/prebuilt/linux-arm64-musl/brotli-65481ff838ce9def.a +0 -0
- package/prebuilt/linux-arm64-musl/uv-f1353f50ff983f3c.a +0 -0
- package/prebuilt/linux-arm64-musl/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/linux-arm64-musl/zstd-015315ac7c784c73.a +0 -0
- package/prebuilt/linux-x64/boringssl-4a47438eaf7e73ba.a +0 -0
- package/prebuilt/linux-x64/brotli-65481ff838ce9def.a +0 -0
- package/prebuilt/linux-x64/uv-f1353f50ff983f3c.a +0 -0
- package/prebuilt/linux-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/linux-x64/zstd-015315ac7c784c73.a +0 -0
- package/prebuilt/linux-x64-musl/boringssl-4a47438eaf7e73ba.a +0 -0
- package/prebuilt/linux-x64-musl/brotli-65481ff838ce9def.a +0 -0
- package/prebuilt/linux-x64-musl/uv-f1353f50ff983f3c.a +0 -0
- package/prebuilt/linux-x64-musl/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/linux-x64-musl/zstd-015315ac7c784c73.a +0 -0
- package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
- package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
- package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
- package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
- package/runtime/include/js_atoms.gen.h +0 -0
- package/runtime/include/js_atoms.h +1 -1
- package/runtime/include/js_bigint.h +1 -1
- package/runtime/include/js_buffer.h +4 -4
- package/runtime/include/js_builtins.h +41 -3
- package/runtime/include/js_console.h +9 -4
- package/runtime/include/js_convert.h +1 -1
- package/runtime/include/js_core.def +5 -4
- package/runtime/include/js_crypto.h +1 -1
- package/runtime/include/js_error.h +1 -1
- package/runtime/include/js_exception.h +21 -1
- package/runtime/include/js_function.h +33 -9
- package/runtime/include/js_gc.h +172 -30
- package/runtime/include/js_generator.h +2 -2
- package/runtime/include/js_host.h +19 -1
- package/runtime/include/js_jobs.h +5 -5
- package/runtime/include/js_module.h +1 -1
- package/runtime/include/js_object.h +142 -15
- package/runtime/include/js_ops.h +107 -5
- package/runtime/include/js_promise.h +3 -3
- package/runtime/include/js_regexp.h +1 -1
- package/runtime/include/js_runtime.h +0 -0
- package/runtime/include/js_symbol.h +1 -1
- package/runtime/include/js_web.h +2 -1
- package/runtime/include/jscell.h +22 -15
- package/runtime/include/jscontext.h +74 -39
- package/runtime/include/jsnum.h +1 -1
- package/runtime/include/jsstring.h +19 -6
- package/runtime/include/jsvalue.h +7 -3
- package/runtime/src/arguments.c +2 -2
- package/runtime/src/array.c +4 -1
- package/runtime/src/arraybuffer.c +19 -7
- package/runtime/src/async_iter.c +4 -3
- package/runtime/src/atoms.c +1 -1
- package/runtime/src/base64.c +2 -2
- package/runtime/src/bigint.c +1 -1
- package/runtime/src/blob.c +22 -9
- package/runtime/src/boringssl_support.cc +1 -1
- package/runtime/src/buffer.c +299 -91
- package/runtime/src/builtin_table.c +1 -1
- package/runtime/src/builtins.c +30 -4
- package/runtime/src/builtins_util.h +0 -0
- package/runtime/src/collections.c +18 -3
- package/runtime/src/components.c +1 -1
- package/runtime/src/console.c +14 -7
- package/runtime/src/convert.c +0 -0
- package/runtime/src/core_all.c +1 -1
- package/runtime/src/crypto.c +1 -1
- package/runtime/src/crypto_asym.c +1 -1
- package/runtime/src/crypto_bn.c +1 -1
- package/runtime/src/crypto_cipher.c +10 -3
- package/runtime/src/crypto_hash.c +1 -1
- package/runtime/src/crypto_host.c +1 -1
- package/runtime/src/crypto_internal.h +1 -1
- package/runtime/src/crypto_kdf.c +1 -1
- package/runtime/src/crypto_key.c +14 -1
- package/runtime/src/crypto_pkey.c +1 -1
- package/runtime/src/crypto_subtle.c +142 -58
- package/runtime/src/crypto_tls.c +1 -1
- package/runtime/src/date.c +5 -2
- package/runtime/src/domexception.c +2 -2
- package/runtime/src/elements.c +56 -17
- package/runtime/src/elements.h +7 -2
- package/runtime/src/encoding.c +1 -1
- package/runtime/src/error.c +4 -4
- package/runtime/src/events.c +23 -14
- package/runtime/src/exception.c +1 -1
- package/runtime/src/fetch.c +241 -17
- package/runtime/src/function.c +31 -19
- package/runtime/src/gc.c +1716 -207
- package/runtime/src/gc_visit.h +87 -0
- package/runtime/src/generator.c +5 -3
- package/runtime/src/global.c +14 -6
- package/runtime/src/headers.c +10 -10
- package/runtime/src/inspect.c +76 -17
- package/runtime/src/internal.h +5 -2
- package/runtime/src/iterate.c +94 -2
- package/runtime/src/iterator.c +1 -1
- package/runtime/src/jobs.c +11 -5
- package/runtime/src/jsnum.c +2 -2
- package/runtime/src/json.c +1 -1
- package/runtime/src/math.c +9 -1
- package/runtime/src/module.c +3 -3
- package/runtime/src/number.c +0 -0
- package/runtime/src/object.c +107 -74
- package/runtime/src/ops.c +21 -3
- package/runtime/src/os.c +192 -1
- package/runtime/src/os.h +33 -2
- package/runtime/src/process.c +73 -8
- package/runtime/src/promise.c +4 -4
- package/runtime/src/proxy.c +1 -1
- package/runtime/src/reflect.c +1 -1
- package/runtime/src/regexp.c +134 -2
- package/runtime/src/regexp_anchor.h +1 -1
- package/runtime/src/regexp_message.h +1 -1
- package/runtime/src/shape.c +10 -19
- package/runtime/src/shape.h +0 -0
- package/runtime/src/streams.c +22 -8
- package/runtime/src/streams.h +0 -0
- package/runtime/src/string.c +64 -25
- package/runtime/src/string_builtins.c +7 -7
- package/runtime/src/symbol.c +2 -7
- package/runtime/src/test_host.c +3 -3
- package/runtime/src/tls_roots.gen.h +102 -620
- package/runtime/src/typedarray.c +8 -3
- package/runtime/src/tznames.gen.h +2 -1
- package/runtime/src/uri.c +1 -1
- package/runtime/src/url.c +42 -39
- package/runtime/src/weak.c +15 -12
- package/runtime/src/web.c +21 -3
- package/runtime/src/wrapper.c +0 -0
- package/runtime/src/writable.c +7 -6
- package/runtime/third_party/ada-idna/LICENSE-MIT +0 -0
- package/runtime/third_party/ada-idna/README.md +7 -7
- package/runtime/third_party/ada-idna/convert.mjs +89 -27
- package/runtime/third_party/ada-idna/idna.c +30 -31
- package/runtime/third_party/ada-idna/idna.h +0 -0
- package/runtime/third_party/ada-idna/idna_tables.h +2272 -1952
- package/runtime/third_party/boringssl/third_party/fiat/LICENSE +15 -0
- package/runtime/third_party/mozilla-ca/LICENSE +373 -0
- package/runtime/third_party/mozilla-ca/README.md +9 -0
- package/runtime/third_party/nodejs/LICENSE +47 -0
- package/runtime/third_party/nodejs/README.md +7 -0
- package/runtime/third_party/quickjs-dtoa/LICENSE +0 -0
- package/runtime/third_party/quickjs-dtoa/README.md +1 -1
- package/runtime/third_party/quickjs-dtoa/dtoa.c +0 -0
- package/runtime/third_party/quickjs-dtoa/dtoa.h +0 -0
- package/runtime/third_party/quickjs-dtoa/dtoa_compat.h +0 -0
- package/runtime/third_party/quickjs-regexp/LICENSE +0 -0
- package/runtime/third_party/quickjs-regexp/README.md +2 -2
- package/runtime/third_party/quickjs-regexp/cutils.c +0 -0
- package/runtime/third_party/quickjs-regexp/cutils.h +0 -0
- package/runtime/third_party/quickjs-regexp/libregexp-opcode.h +0 -0
- package/runtime/third_party/quickjs-regexp/libregexp.c +0 -0
- package/runtime/third_party/quickjs-regexp/libregexp.h +0 -0
- package/runtime/third_party/quickjs-regexp/libunicode-table.h +0 -0
- package/runtime/third_party/quickjs-regexp/libunicode.c +0 -0
- package/runtime/third_party/quickjs-regexp/libunicode.h +0 -0
- package/runtime/third_party/unicode/LICENSE +39 -0
- package/runtime/third_party/unicode/README.md +9 -0
- package/runtime/third_party/v8-ieee754/LICENSE +0 -0
- package/runtime/third_party/v8-ieee754/LICENSE.fdlibm +10 -0
- package/runtime/third_party/v8-ieee754/README.md +3 -2
- package/runtime/third_party/v8-ieee754/convert.py +0 -0
- package/runtime/third_party/v8-ieee754/ieee754.c +0 -0
- package/runtime/third_party/v8-ieee754/ieee754.h +0 -0
- package/runtime/tools/regexp_compile.c +2 -2
package/runtime/src/gc.c
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
/* Heap and garbage collector
|
|
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
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
* - stopping the world clears the bits of the cells
|
|
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
|
|
36
|
-
|
|
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
|
-
/*
|
|
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
|
|
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
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
/*
|
|
202
|
-
*
|
|
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
|
-
|
|
221
|
-
for (uint32_t i = b->cell_count; i
|
|
222
|
-
|
|
223
|
-
c
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
/*
|
|
237
|
-
*
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
-
/*
|
|
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 >=
|
|
266
|
-
|
|
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
|
|
275
|
-
*
|
|
276
|
-
* the
|
|
277
|
-
static
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
JSBlock *b =
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
293
|
-
} else {
|
|
294
|
-
link = &b->next;
|
|
403
|
+
*out = NULL;
|
|
295
404
|
}
|
|
296
|
-
|
|
297
|
-
*list = free_list;
|
|
298
|
-
return 1;
|
|
405
|
+
return true;
|
|
299
406
|
}
|
|
300
407
|
|
|
301
408
|
static int should_collect(const JSHeap *h) {
|
|
302
|
-
|
|
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
|
-
|
|
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
|
-
/*
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
524
|
+
block_init(h, b, sc);
|
|
525
|
+
use_block(h, a, b, false);
|
|
526
|
+
next_window(h, a);
|
|
527
|
+
return;
|
|
354
528
|
}
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
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
|
-
/*
|
|
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
|
|
385
|
-
|
|
386
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
-
|
|
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
|
-
|
|
508
|
-
|
|
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
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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); /*
|
|
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
|
|
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
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
619
|
-
|
|
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,
|
|
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
|
|
648
|
-
|
|
649
|
-
for (
|
|
650
|
-
for (
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
-
|
|
695
|
-
for (
|
|
696
|
-
|
|
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
|
-
|
|
699
|
-
|
|
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(®s, 0, sizeof regs);
|
|
2032
|
+
#if defined(__clang__) || defined(__GNUC__)
|
|
2033
|
+
__builtin_unwind_init();
|
|
2034
|
+
#endif
|
|
2035
|
+
setjmp(regs);
|
|
2036
|
+
start_marking_at(ctx, ®s);
|
|
2037
|
+
(void)*(volatile char *)®s;
|
|
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
|
|
2072
|
+
static JS_NOINLINE void collect_spilled(JSContext *ctx, bool full) {
|
|
707
2073
|
jmp_buf regs;
|
|
708
2074
|
memset(®s, 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, ®s);
|
|
2079
|
+
collect(ctx, ®s, full);
|
|
714
2080
|
(void)*(volatile char *)®s; /* 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.
|
|
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
|
|
2305
|
+
js_install_recorded_components(ctx); /* the program's optional built-ins */
|
|
797
2306
|
return ctx;
|
|
798
2307
|
}
|
|
799
2308
|
|