Skip to content

Commit ccbaed9

Browse files
deps: bump mimalloc to dev3 (bun-dev3-v2) (#29251)
Upgrades mimalloc to v3 via [`oven-sh/mimalloc@bun-dev3-v2`](https://github.com/oven-sh/mimalloc/tree/bun-dev3-v2) (2e94216f), which carries on top of upstream dev3: - Thread-safety fixes for `mi_heap_delete` racing concurrent `mi_free`, and `mi_heap_new` while another thread is in `mi_heap_destroy` (both reproduce as assertion failures / ASAN UAF under load) - Fix for the 1025th `mi_heap_new()` walking an uninitialized chunkmap → OOB write - `pthread_atfork`-based fork handling so children of multi-threaded parents can allocate - macOS fixed TLS slots moved 108/109→175/176 (avoid Swift runtime keys) - Recursive `mi_thread_init` self-deadlock fix in `_mi_os_get_aligned_hint` Zig changes are a tighter subset of #26214: - v1/v2 APIs removed from bindings: `mi_heap_{get,set}_default`, `mi_heap_get_backing`, `mi_heap_check_owned`, `mi_heap_contains_block`. Added `mi_heap_main`, `mi_heap_contains`. - `MimallocArena` vtable split into `heap_allocator_vtable` (per-`mi_heap_new` arena) + `global_mimalloc_vtable` (`mi_malloc`/`mi_free` directly). `Default`/`getThreadLocalDefault` route through global; no more cached per-thread heap pointer. - Differs from #26214: `Borrowed.getDefault()` wraps `mi_heap_main()` (not `undefined`) so `gc()`/`ownsPtr()` stay valid; `ownsPtr` uses per-heap `mi_heap_contains` (not `mi_check_owned`); `downcast` only accepts owned-heap vtable. Build: `MI_NO_OPT_ARCH=ON`, Windows lib name dropped `-static` suffix. Fixes #26762 Closes #26214 - Also upgrades mimalloc to v3; explicitly superseded by this PR Closes #21875 - Earlier mimalloc v3 upgrade attempt Closes #22043 - Earlier mimalloc v2 upgrade attempt Closes #22894 - Another mimalloc version bump --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent f7e9441 commit ccbaed9

File tree

8 files changed

+170
-52
lines changed

8 files changed

+170
-52
lines changed

scripts/build/deps/mimalloc.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import type { Dependency, NestedCmakeBuild, Provides } from "../source.ts";
77

8-
const MIMALLOC_COMMIT = "1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a";
8+
const MIMALLOC_COMMIT = "9a5e1f52cdf4662f9590b69de104a4469140796f";
99

1010
export const mimalloc: Dependency = {
1111
name: "mimalloc",
@@ -68,8 +68,13 @@ export const mimalloc: Dependency = {
6868
// so UBSan doesn't false-positive on mimalloc's type punning.
6969
args.MI_DEBUG_UBSAN = "ON";
7070
} else if (cfg.darwin) {
71-
args.MI_OVERRIDE = "OFF";
72-
args.MI_OSX_ZONE = "OFF";
71+
// Register the mimalloc malloc_zone so leaks/heap/vmmap/Instruments can
72+
// enumerate our allocations and the fork-prepare/child hooks fire.
73+
// Interpose is OFF because we link the .o statically; the __interpose
74+
// section only works for inserted dylibs. The zone-swap path in
75+
// alloc-override-zone.c handles making mimalloc the default zone.
76+
args.MI_OVERRIDE = "ON";
77+
args.MI_OSX_ZONE = "ON";
7378
args.MI_OSX_INTERPOSE = "OFF";
7479
} else if (cfg.linux) {
7580
args.MI_OVERRIDE = "ON";
@@ -91,10 +96,10 @@ export const mimalloc: Dependency = {
9196
args.MI_TRACK_VALGRIND = "ON";
9297
}
9398

94-
// If mimalloc gets bumped to a version with MI_OPT_ARCH: pass
95-
// MI_NO_OPT_ARCH=ON to stop it setting -march=armv8.1-a on arm64
96-
// (SIGILLs on ARMv8.0 CPUs). Current pin has no arch-detection logic
97-
// so our global -march=armv8-a+crc (via CMAKE_CXX_FLAGS) is sufficient.
99+
// dev3 grew MI_OPT_ARCH which sets -march=armv8.1-a on arm64 — that
100+
// SIGILLs on ARMv8.0 CPUs. Explicitly disable it; our global
101+
// -march=armv8-a+crc (via CMAKE_CXX_FLAGS) is sufficient.
102+
args.MI_NO_OPT_ARCH = "ON";
98103

99104
// ─── Windows: silence the vendored-C-as-C++ warning flood ───
100105
// MI_USE_CXX=ON means .c files compile as C++. clang-cl then complains
@@ -121,7 +126,7 @@ export const mimalloc: Dependency = {
121126
// to override this, so we have to mirror its naming logic.
122127
let libname: string;
123128
if (cfg.windows) {
124-
libname = cfg.debug ? "mimalloc-static-debug" : "mimalloc-static";
129+
libname = cfg.debug ? "mimalloc-debug" : "mimalloc";
125130
} else if (cfg.debug) {
126131
libname = cfg.asan ? "mimalloc-asan-debug" : "mimalloc-debug";
127132
} else {

scripts/verify-baseline-static/allowlist-aarch64.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ __aarch64_ldclr1_acq_rel [LSE]
9494
__aarch64_ldclr2_acq_rel [LSE]
9595
__aarch64_ldclr4_acq_rel [LSE]
9696
__aarch64_ldclr8_acq_rel [LSE]
97+
__aarch64_ldclr8_relax [LSE]
9798
__aarch64_ldeor1_acq_rel [LSE]
9899
__aarch64_ldeor2_acq_rel [LSE]
99100
__aarch64_ldeor4_acq_rel [LSE]
@@ -102,6 +103,7 @@ __aarch64_ldset1_acq_rel [LSE]
102103
__aarch64_ldset2_acq_rel [LSE]
103104
__aarch64_ldset4_acq_rel [LSE]
104105
__aarch64_ldset8_acq_rel [LSE]
106+
__aarch64_ldset8_relax [LSE]
105107
__aarch64_swp1_acq [LSE]
106108
__aarch64_swp1_acq_rel [LSE]
107109
__aarch64_swp1_relax [LSE]
@@ -110,6 +112,8 @@ __aarch64_swp4_acq [LSE]
110112
__aarch64_swp4_acq_rel [LSE]
111113
__aarch64_swp4_rel [LSE]
112114
__aarch64_swp8_acq_rel [LSE]
115+
__aarch64_swp8_rel [LSE]
116+
__aarch64_swp8_relax [LSE]
113117

114118

115119
# ----------------------------------------------------------------------------

src/allocators/MimallocArena.zig

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ const Self = @This();
88
///
99
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
1010
pub const Default = struct {
11-
pub fn allocator(self: Default) std.mem.Allocator {
12-
_ = self;
13-
return Borrowed.getDefault().allocator();
11+
pub fn allocator(_: Default) std.mem.Allocator {
12+
return .{ .ptr = undefined, .vtable = &global_mimalloc_vtable };
1413
}
1514
};
1615

@@ -23,11 +22,20 @@ pub const Borrowed = struct {
2322
#heap: BorrowedHeap,
2423

2524
pub fn allocator(self: Borrowed) std.mem.Allocator {
26-
return .{ .ptr = self.#heap, .vtable = &c_allocator_vtable };
25+
return .{ .ptr = self.#heap, .vtable = &heap_allocator_vtable };
2726
}
2827

28+
/// Prefer `Default.allocator()` / `getThreadLocalDefault()` for a thread-safe
29+
/// global allocator. This returns the process-wide main heap so that
30+
/// `gc()` / `ownsPtr()` on the result remain meaningful.
2931
pub fn getDefault() Borrowed {
30-
return .{ .#heap = getThreadHeap() };
32+
const heap = mimalloc.mi_heap_main();
33+
if (comptime !safety_checks) return .{ .#heap = heap };
34+
const S = struct {
35+
threadlocal var dbg: ?DebugHeap = null;
36+
};
37+
if (S.dbg == null) S.dbg = .{ .inner = heap, .thread_lock = .initLocked() };
38+
return .{ .#heap = &S.dbg.? };
3139
}
3240

3341
pub fn gc(self: Borrowed) void {
@@ -42,7 +50,7 @@ pub const Borrowed = struct {
4250
}
4351

4452
pub fn ownsPtr(self: Borrowed, ptr: *const anyopaque) bool {
45-
return mimalloc.mi_heap_check_owned(self.getMimallocHeap(), ptr);
53+
return mimalloc.mi_heap_contains(self.getMimallocHeap(), ptr);
4654
}
4755

4856
fn fromOpaque(ptr: *anyopaque) Borrowed {
@@ -81,8 +89,8 @@ pub const Borrowed = struct {
8189

8290
pub fn downcast(std_alloc: std.mem.Allocator) Borrowed {
8391
bun.assertf(
84-
isInstance(std_alloc),
85-
"not a MimallocArena (vtable is {*})",
92+
std_alloc.vtable == &heap_allocator_vtable,
93+
"not an owned MimallocArena heap (vtable is {*})",
8694
.{std_alloc.vtable},
8795
);
8896
return .fromOpaque(std_alloc.ptr);
@@ -98,19 +106,6 @@ const DebugHeap = struct {
98106
pub const deinit = void;
99107
};
100108

101-
threadlocal var thread_heap: if (safety_checks) ?DebugHeap else void = if (safety_checks) null;
102-
103-
fn getThreadHeap() BorrowedHeap {
104-
if (comptime !safety_checks) return mimalloc.mi_heap_get_default();
105-
if (thread_heap == null) {
106-
thread_heap = .{
107-
.inner = mimalloc.mi_heap_get_default(),
108-
.thread_lock = .initLocked(),
109-
};
110-
}
111-
return &thread_heap.?;
112-
}
113-
114109
const log = bun.Output.scoped(.mimalloc, .hidden);
115110

116111
pub fn allocator(self: Self) std.mem.Allocator {
@@ -121,13 +116,11 @@ pub fn borrow(self: Self) Borrowed {
121116
return .{ .#heap = if (comptime safety_checks) self.#heap.get() else self.#heap };
122117
}
123118

124-
/// Internally, mimalloc calls mi_heap_get_default()
125-
/// to get the default heap.
126-
/// It uses pthread_getspecific to do that.
127-
/// We can save those extra calls if we just do it once in here
119+
/// In v3, `mi_malloc`/`mi_free` are already thread-local-fast — there is no
120+
/// per-thread default heap to cache. Route through the global vtable.
128121
pub fn getThreadLocalDefault() std.mem.Allocator {
129122
if (bun.Environment.enable_asan) return bun.default_allocator;
130-
return Borrowed.getDefault().allocator();
123+
return .{ .ptr = undefined, .vtable = &global_mimalloc_vtable };
131124
}
132125

133126
pub fn backingAllocator(_: Self) std.mem.Allocator {
@@ -251,17 +244,43 @@ fn vtable_remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize
251244
return @ptrCast(value);
252245
}
253246

247+
fn global_vtable_alloc(_: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
248+
log("Malloc: {d}\n", .{len});
249+
const ptr: ?*anyopaque = if (mimalloc.mustUseAlignedAlloc(alignment))
250+
mimalloc.mi_malloc_aligned(len, alignment.toByteUnits())
251+
else
252+
mimalloc.mi_malloc(len);
253+
return if (ptr) |p| @ptrCast(p) else null;
254+
}
255+
256+
fn global_vtable_resize(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
257+
return mimalloc.mi_expand(buf.ptr, new_len) != null;
258+
}
259+
260+
fn global_vtable_remap(_: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
261+
return @ptrCast(mimalloc.mi_realloc_aligned(buf.ptr, new_len, alignment.toByteUnits()));
262+
}
263+
254264
pub fn isInstance(alloc: std.mem.Allocator) bool {
255-
return alloc.vtable == &c_allocator_vtable;
265+
return alloc.vtable == &heap_allocator_vtable or alloc.vtable == &global_mimalloc_vtable;
256266
}
257267

258-
const c_allocator_vtable = std.mem.Allocator.VTable{
268+
/// VTable for owned heaps created with `mi_heap_new`.
269+
const heap_allocator_vtable = std.mem.Allocator.VTable{
259270
.alloc = vtable_alloc,
260271
.resize = vtable_resize,
261272
.remap = vtable_remap,
262273
.free = vtable_free,
263274
};
264275

276+
/// VTable for the process-wide default allocator (`mi_malloc`/`mi_free`).
277+
const global_mimalloc_vtable = std.mem.Allocator.VTable{
278+
.alloc = global_vtable_alloc,
279+
.resize = global_vtable_resize,
280+
.remap = global_vtable_remap,
281+
.free = vtable_free,
282+
};
283+
265284
const std = @import("std");
266285
const Alignment = std.mem.Alignment;
267286

src/allocators/mimalloc.zig

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,15 @@ pub const Heap = opaque {
6060
return mi_heap_realloc(self, p, newsize);
6161
}
6262

63-
pub fn isOwned(self: *Heap, p: ?*anyopaque) bool {
64-
return mi_heap_check_owned(self, p);
63+
pub fn isOwned(self: *Heap, p: ?*const anyopaque) bool {
64+
return mi_heap_contains(self, p);
6565
}
6666
};
6767
pub extern fn mi_heap_new() ?*Heap;
6868
pub extern fn mi_heap_delete(heap: *Heap) void;
6969
pub extern fn mi_heap_destroy(heap: *Heap) void;
70-
pub extern fn mi_heap_set_default(heap: *Heap) *Heap;
71-
pub extern fn mi_heap_get_default() *Heap;
72-
pub extern fn mi_heap_get_backing() *Heap;
70+
pub extern fn mi_heap_main() *Heap;
71+
pub extern fn mi_heap_contains(heap: *const Heap, p: ?*const anyopaque) bool;
7372
pub extern fn mi_heap_collect(heap: *Heap, force: bool) void;
7473
pub extern fn mi_heap_malloc(heap: *Heap, size: usize) ?*anyopaque;
7574
pub extern fn mi_heap_zalloc(heap: *Heap, size: usize) ?*anyopaque;
@@ -102,8 +101,6 @@ pub extern fn mi_heap_rezalloc_aligned(heap: *Heap, p: ?*anyopaque, newsize: usi
102101
pub extern fn mi_heap_rezalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newsize: usize, alignment: usize, offset: usize) ?*anyopaque;
103102
pub extern fn mi_heap_recalloc_aligned(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize) ?*anyopaque;
104103
pub extern fn mi_heap_recalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize, offset: usize) ?*anyopaque;
105-
pub extern fn mi_heap_contains_block(heap: *Heap, p: *const anyopaque) bool;
106-
pub extern fn mi_heap_check_owned(heap: *Heap, p: *const anyopaque) bool;
107104
pub extern fn mi_check_owned(p: ?*const anyopaque) bool;
108105
pub const struct_mi_heap_area_s = extern struct {
109106
blocks: ?*anyopaque,
@@ -112,6 +109,7 @@ pub const struct_mi_heap_area_s = extern struct {
112109
used: usize,
113110
block_size: usize,
114111
full_block_size: usize,
112+
reserved1: ?*anyopaque,
115113
};
116114
pub const mi_heap_area_t = struct_mi_heap_area_s;
117115
pub const mi_block_visit_fun = *const fn (?*const Heap, [*c]const mi_heap_area_t, ?*anyopaque, usize, ?*anyopaque) callconv(.c) bool;

src/bun.js/modules/BunJSCModule.h

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
#include "JSDOMConvertBase.h"
4747
#include "ZigSourceProvider.h"
4848
#include "mimalloc.h"
49+
extern "C" char* mi_stats_get_json(size_t, char*);
50+
extern "C" char* mi_heap_dump_json(bool include_blocks, bool hash_addresses);
4951

5052
#include <JavaScriptCore/ControlFlowProfiler.h>
5153

@@ -219,7 +221,7 @@ createMemoryFootprintStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
219221

220222
JSC_DECLARE_HOST_FUNCTION(functionMemoryUsageStatistics);
221223
JSC_DEFINE_HOST_FUNCTION(functionMemoryUsageStatistics,
222-
(JSGlobalObject * globalObject, CallFrame*))
224+
(JSGlobalObject * globalObject, CallFrame* callFrame))
223225
{
224226

225227
auto& vm = JSC::getVM(globalObject);
@@ -356,6 +358,34 @@ JSC_DEFINE_HOST_FUNCTION(functionMemoryUsageStatistics,
356358
#endif
357359
#endif
358360

361+
mi_collect(false);
362+
if (char* json = mi_stats_get_json(0, nullptr)) {
363+
JSValue parsed = JSONParse(globalObject, String::fromUTF8(json));
364+
mi_free(json);
365+
object->putDirect(vm, Identifier::fromString(vm, "mimalloc"_s),
366+
parsed.isEmpty() ? jsNull() : parsed);
367+
}
368+
369+
// heapStats({ dump: true | "blocks" }) -> per-heap/per-page (and per-block) live snapshot.
370+
JSValue arg0 = callFrame->argument(0);
371+
if (arg0.isObject()) {
372+
JSValue dump = arg0.getObject()->get(globalObject, Identifier::fromString(vm, "dump"_s));
373+
if (dump.toBoolean(globalObject)) {
374+
const bool includeBlocks = dump.isString() && dump.toWTFString(globalObject) == "blocks"_s;
375+
#if BUN_DEBUG
376+
const bool hashAddresses = false;
377+
#else
378+
const bool hashAddresses = true;
379+
#endif
380+
if (char* json = mi_heap_dump_json(includeBlocks, hashAddresses)) {
381+
JSValue parsed = JSONParse(globalObject, String::fromUTF8(json));
382+
mi_free(json);
383+
object->putDirect(vm, Identifier::fromString(vm, "mimallocDump"_s),
384+
parsed.isEmpty() ? jsNull() : parsed);
385+
}
386+
}
387+
}
388+
359389
return JSValue::encode(object);
360390
}
361391

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { heapStats } from "bun:jsc";
2+
import { describe, expect, test } from "bun:test";
3+
4+
describe("heapStats() mimalloc integration", () => {
5+
test("mimalloc aggregate stats are present", () => {
6+
const s = heapStats();
7+
expect(s.mimalloc).toBeDefined();
8+
expect(s.mimalloc.mimalloc_version).toBeGreaterThan(3000);
9+
expect(s.mimalloc.pages.current).toBeGreaterThan(0);
10+
expect(s.mimalloc.committed.current).toBeGreaterThan(0);
11+
expect(Array.isArray(s.mimalloc.malloc_bins)).toBe(true);
12+
});
13+
14+
test("heapStats({dump: true}) returns per-heap pages", () => {
15+
const s = heapStats({ dump: true });
16+
expect(s.mimallocDump).toBeDefined();
17+
expect(Array.isArray(s.mimallocDump.heaps)).toBe(true);
18+
expect(s.mimallocDump.heaps.length).toBeGreaterThan(0);
19+
const main = s.mimallocDump.heaps.find((h: any) => h.seq === 0);
20+
expect(main).toBeDefined();
21+
expect(Array.isArray(main.pages)).toBe(true);
22+
expect(main.pages.length).toBeGreaterThan(0);
23+
const page = main.pages[0];
24+
expect(typeof page.id).toBe("number");
25+
expect(page.block_size).toBeGreaterThan(0);
26+
expect(page.used).toBeGreaterThanOrEqual(0);
27+
expect(page.reserved).toBeGreaterThan(0);
28+
expect(typeof page.thread_id).toBe("number");
29+
// pages-only mode: no blocks
30+
expect(main.blocks).toBeUndefined();
31+
});
32+
33+
test("heapStats({dump: 'blocks'}) includes per-block ids", () => {
34+
const s = heapStats({ dump: "blocks" });
35+
const main = s.mimallocDump.heaps.find((h: any) => h.seq === 0);
36+
expect(Array.isArray(main.blocks)).toBe(true);
37+
expect(main.blocks.length).toBeGreaterThan(0);
38+
const [id, size] = main.blocks[0];
39+
expect(typeof id).toBe("number");
40+
expect(size).toBeGreaterThan(0);
41+
// every block size should match some page's block_size
42+
const pageSizes = new Set(main.pages.map((p: any) => p.block_size));
43+
for (const [, sz] of main.blocks.slice(0, 50)) {
44+
expect(pageSizes.has(sz)).toBe(true);
45+
}
46+
});
47+
48+
test("dump reflects new heaps and allocations", () => {
49+
const before = heapStats({ dump: true }).mimallocDump.heaps.length;
50+
// MimallocArena is internal; trigger via something that creates a heap.
51+
// Transpiler creates a per-call arena.
52+
const t = new Bun.Transpiler();
53+
const out = t.transformSync("export const x = 1");
54+
expect(out.length).toBeGreaterThan(0);
55+
const after = heapStats({ dump: true }).mimallocDump.heaps;
56+
// Either a new heap was created (and may already be destroyed), or main grew.
57+
// We assert the dump is still well-formed and >= before.
58+
expect(after.length).toBeGreaterThanOrEqual(1);
59+
for (const h of after) {
60+
expect(typeof h.seq).toBe("number");
61+
expect(Array.isArray(h.pages)).toBe(true);
62+
}
63+
void before;
64+
});
65+
});

test/js/bun/perf/static-initializers.test.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,12 @@ describe("static initializers", () => {
5959
.map(a => a.trim())
6060
.filter(line => line.includes("running initializer") && line.includes(bunExe()));
6161

62-
// On both architectures, we have one initializer "__GLOBAL__sub_I_static.c".
63-
// On arm64, mimalloc v3 adds one more static initializer (total: 2).
64-
// On x86_64, we also have:
65-
// - one from ___cpu_indicator_init due to our CPU feature detection
66-
// - one from mimalloc v3
67-
// (total: 3)
62+
// mimalloc v3 with MI_OSX_ZONE=ON contributes three: __GLOBAL__sub_I_static.c,
63+
// mi_process_attach, and _mi_macos_override_malloc (the zone-swap constructor).
64+
// On x86_64 there is also ___cpu_indicator_init from CPU feature detection.
6865
expect(
6966
bunInitializers.length,
7067
`Do not add static initializers to Bun. Static initializers are called when Bun starts up, regardless of whether you use the variables or not. This makes Bun slower.`,
71-
).toBe(process.arch === "arm64" ? 1 : 2);
68+
).toBe(process.arch === "arm64" ? 3 : 4);
7269
});
7370
});

test/js/node/process/process.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ it("process.versions", () => {
275275
const expectedVersions = {
276276
boringssl: "4f4f5ef8ebc6e23cbf393428f0ab1b526773f7ac",
277277
libarchive: "9525f90ca4bd14c7b335e2f8c84a4607b0af6bdf",
278-
mimalloc: "1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a",
278+
mimalloc: "9a5e1f52cdf4662f9590b69de104a4469140796f",
279279
picohttpparser: "066d2b1e9ab820703db0837a7255d92d30f0c9f5",
280280
zlib: "886098f3f339617b4243b286f5ed364b9989e245",
281281
tinycc: "12882eee073cfe5c7621bcfadf679e1372d4537b",

0 commit comments

Comments
 (0)