mlx-bun 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +107 -32
  2. package/THIRD_PARTY_LICENSES.md +10 -0
  3. package/docs/archive/mlx-bun-lab-report.html +275 -0
  4. package/docs/archive/wwdc-mlx-bun.html +848 -0
  5. package/docs/design/audio-input-plan.md +356 -0
  6. package/docs/design/batching-perf-path.md +58 -35
  7. package/docs/design/batching-v2-plan.md +23 -9
  8. package/docs/design/bucketing-stage.md +2 -1
  9. package/docs/design/colibri-glm52-port.md +1479 -0
  10. package/docs/design/compat-cli-surface-design.md +4 -3
  11. package/docs/design/decode-speed-program.md +172 -0
  12. package/docs/design/docs-reorg-plan.md +1 -1
  13. package/docs/design/dspark-serving-program.md +371 -0
  14. package/docs/design/dspark-speculative-decoding.md +167 -5
  15. package/docs/design/faithful-l1-consolidation.md +310 -0
  16. package/docs/design/generic-model-support.md +8 -5
  17. package/docs/design/grammar-spec-batching-integration.md +301 -0
  18. package/docs/design/minicpm5-decode-megakernel.md +16 -11
  19. package/docs/design/mlx-lm-tool-parity-plan.md +11 -1
  20. package/docs/design/omlx-adoption-map.md +14 -6
  21. package/docs/design/orpo-training.md +34 -30
  22. package/docs/design/paged-kv-cache.md +214 -0
  23. package/docs/design/parallel-slots.md +82 -49
  24. package/docs/design/parity-tier-dag.md +43 -33
  25. package/docs/design/pre-colibri-stabilization.md +329 -0
  26. package/docs/design/repo-cleanup-plan.md +189 -0
  27. package/docs/design/runtime-isolation.md +19 -3
  28. package/docs/design/segmented-backward-training.md +3 -2
  29. package/docs/design/ssd-kv-cold-tier.md +130 -0
  30. package/docs/design/structured-output.md +484 -0
  31. package/docs/design/the-dreaming-master-plan.md +2 -3
  32. package/docs/design/turboquant-kv.md +235 -0
  33. package/docs/design/unified-engine-frontier-plan.md +774 -0
  34. package/docs/design/web-chat-beat-matrix.md +315 -0
  35. package/docs/design/web-chat-redesign.md +1112 -0
  36. package/docs/investigations/colibri-oracle-pin.md +1284 -0
  37. package/docs/investigations/dspark-handoff.md +182 -16
  38. package/docs/investigations/indexshare-performance-spike.md +357 -0
  39. package/docs/investigations/lab-build-journal.md +2 -2
  40. package/docs/investigations/openwiki-evaluation.md +249 -0
  41. package/docs/planning/IDEAS.md +14 -0
  42. package/docs/planning/PRODUCT_ROADMAP.md +2 -3
  43. package/docs/planning/chat-ui-vision-memory-first.md +199 -0
  44. package/docs/planning/chat-ui-vision.md +50 -0
  45. package/docs/planning/memory-docs-and-dag-plan.md +15 -6
  46. package/docs/planning/release-notes-v0.0.11.md +239 -0
  47. package/docs/planning/release-notes-v0.0.12.md +80 -0
  48. package/docs/reference/cli.md +63 -18
  49. package/docs/reference/distribution.md +37 -6
  50. package/docs/reference/embedding.md +40 -16
  51. package/docs/reference/features-matrix.md +99 -0
  52. package/docs/reference/library-api.md +69 -20
  53. package/docs/reference/memory.md +26 -6
  54. package/docs/reference/models.md +60 -3
  55. package/docs/reference/orpo-quickstart.md +3 -4
  56. package/docs/reference/server-api.md +717 -24
  57. package/docs/reference/server-config.md +265 -129
  58. package/docs/reference/training.md +24 -16
  59. package/package.json +5 -3
  60. package/src/anthropic.ts +67 -44
  61. package/src/audio/conformer.ts +647 -0
  62. package/src/audio/decode.ts +133 -0
  63. package/src/audio/features.ts +202 -0
  64. package/src/audio/transcode.ts +67 -0
  65. package/src/chat-template.ts +155 -10
  66. package/src/cli.ts +483 -113
  67. package/src/config.ts +116 -24
  68. package/src/diffusion/diffusion-generate.ts +14 -8
  69. package/src/download.ts +143 -12
  70. package/src/eval/README.md +7 -6
  71. package/src/eval/ifeval.ts +63 -120
  72. package/src/eval/kl.ts +59 -3
  73. package/src/eval/quality-db.ts +2 -2
  74. package/src/eval/runner.ts +17 -8
  75. package/src/eval/tasks/ifeval.ts +167 -31
  76. package/src/expert-io.ts +391 -0
  77. package/src/expert-offload.ts +15 -2
  78. package/src/expert-residency.ts +1134 -0
  79. package/src/expert-usage.ts +415 -0
  80. package/src/flags.ts +13 -0
  81. package/src/generate.ts +438 -35
  82. package/src/grammar.ts +585 -0
  83. package/src/hf-push.ts +1 -1
  84. package/src/hub-rest.ts +242 -0
  85. package/src/index.ts +14 -2
  86. package/src/jobs/runner.ts +37 -18
  87. package/src/kv-store.ts +586 -93
  88. package/src/lora.ts +85 -14
  89. package/src/md-modules.d.ts +42 -0
  90. package/src/media-fetch.ts +426 -0
  91. package/src/memory/rest.ts +245 -0
  92. package/src/memory/vault.ts +74 -0
  93. package/src/mlx/array.ts +149 -19
  94. package/src/mlx/autograd.ts +53 -16
  95. package/src/mlx/checkpoint.ts +46 -13
  96. package/src/mlx/custom-vjp.ts +83 -35
  97. package/src/mlx/ffi.ts +27 -3
  98. package/src/mlx/metal-kernel.ts +7 -4
  99. package/src/mlx/ops.ts +153 -23
  100. package/src/mlx/turboquant-ops.ts +831 -0
  101. package/src/mlx/turboquant-tables.ts +237 -0
  102. package/src/model/batched-mask.ts +62 -1
  103. package/src/model/batched-quant.ts +206 -0
  104. package/src/model/batched-rotating-quant.ts +346 -0
  105. package/src/model/batched-rotating.ts +46 -1
  106. package/src/model/compiled-decode.ts +4 -15
  107. package/src/model/factory.ts +103 -6
  108. package/src/model/gemma4-base.ts +395 -39
  109. package/src/model/gemma4.ts +122 -33
  110. package/src/model/generated/gemma4-12b.ts +9 -13
  111. package/src/model/generated/gemma4-26b.ts +10 -5
  112. package/src/model/generated/gemma4-e4b.ts +14 -9
  113. package/src/model/glm52-atlas.ts +511 -0
  114. package/src/model/glm52-cache.ts +706 -0
  115. package/src/model/glm52-config.ts +262 -0
  116. package/src/model/glm52-container.ts +305 -0
  117. package/src/model/glm52-coupling.ts +388 -0
  118. package/src/model/glm52-dsa.ts +602 -0
  119. package/src/model/glm52-expert-layout.ts +253 -0
  120. package/src/model/glm52-layout.ts +152 -0
  121. package/src/model/glm52-memory.ts +473 -0
  122. package/src/model/glm52-mla.ts +786 -0
  123. package/src/model/glm52-moe.ts +223 -0
  124. package/src/model/glm52-pilot.ts +537 -0
  125. package/src/model/glm52-reference.ts +445 -0
  126. package/src/model/glm52-residency.ts +557 -0
  127. package/src/model/glm52-resident-weights.ts +135 -0
  128. package/src/model/glm52-streamed-experts.ts +453 -0
  129. package/src/model/glm52-streamed-metal.ts +610 -0
  130. package/src/model/glm52-weights.ts +503 -0
  131. package/src/model/glm52.ts +1153 -0
  132. package/src/model/minicpm5.ts +58 -102
  133. package/src/model/paged-kv.ts +253 -0
  134. package/src/model/qwen3-delta.ts +76 -15
  135. package/src/model/qwen3-moe.ts +404 -0
  136. package/src/model/qwen3.ts +9 -8
  137. package/src/model/qwen3_5.ts +88 -23
  138. package/src/model/support.ts +19 -1
  139. package/src/model/universal/dense.ts +40 -5
  140. package/src/model/universal/rope.ts +36 -21
  141. package/src/native/expert_io.c +523 -0
  142. package/src/native-pack.ts +6 -4
  143. package/src/pi-web.ts +1299 -82
  144. package/src/preflight.ts +27 -8
  145. package/src/prompt-cache.ts +293 -33
  146. package/src/quantize/quantizer.ts +16 -1
  147. package/src/registry.ts +57 -2
  148. package/src/responses.ts +62 -49
  149. package/src/serve/batch-scheduler.ts +1050 -78
  150. package/src/serve/generation-gateway.ts +252 -24
  151. package/src/serve/isolate.ts +417 -0
  152. package/src/serve/lane-registry.ts +44 -0
  153. package/src/serve/session-search.ts +326 -0
  154. package/src/server.ts +1701 -175
  155. package/src/spec/assistant-source.ts +150 -0
  156. package/src/spec/deepspec-source.ts +143 -0
  157. package/src/spec/dflash-source.ts +124 -0
  158. package/src/spec/dspark/ab-stats.ts +136 -0
  159. package/src/spec/dspark/calibration.ts +112 -0
  160. package/src/spec/dspark/deepspec-module.ts +801 -0
  161. package/src/spec/dspark/generate-dflash.ts +72 -20
  162. package/src/spec/dspark/loader.ts +23 -0
  163. package/src/spec/dspark/module-dflash.ts +278 -36
  164. package/src/spec/dspark/quantize-drafter.ts +91 -0
  165. package/src/spec/generate.ts +59 -25
  166. package/src/spec/glm52-mtp-source.ts +392 -0
  167. package/src/spec/ngram-source.ts +132 -0
  168. package/src/spec/serve-loop.ts +617 -0
  169. package/src/spec/source.ts +118 -0
  170. package/src/spec/two-model.ts +171 -0
  171. package/src/ssd-cache.ts +111 -35
  172. package/src/tokenizer.ts +288 -2
  173. package/src/tool-approvals.ts +131 -0
  174. package/src/tool-call.ts +438 -22
  175. package/src/train/flash-cce.ts +2 -2
  176. package/src/train/job.ts +0 -12
  177. package/src/train/lora-params.ts +17 -6
  178. package/src/train/trainer.ts +60 -30
  179. package/src/vision/prompt.ts +217 -48
  180. package/src/web/app.html +1197 -1791
  181. package/src/web/app.js +5694 -0
  182. package/src/web/icon.svg +13 -0
  183. package/src/web/manifest.webmanifest +24 -0
  184. package/src/web/src/adapters-panel.ts +288 -0
  185. package/src/web/src/api.ts +57 -0
  186. package/src/web/src/assistant.ts +485 -0
  187. package/src/web/src/chat.ts +1050 -0
  188. package/src/web/src/composer.ts +959 -0
  189. package/src/web/src/dataset.ts +132 -0
  190. package/src/web/src/finetune.ts +267 -0
  191. package/src/web/src/hub.ts +428 -0
  192. package/src/web/src/main.ts +98 -0
  193. package/src/web/src/markdown.ts +0 -0
  194. package/src/web/src/memory-panel.ts +572 -0
  195. package/src/web/src/model-picker.ts +159 -0
  196. package/src/web/src/palette.ts +346 -0
  197. package/src/web/src/protocol.ts +62 -0
  198. package/src/web/src/quantize.ts +167 -0
  199. package/src/web/src/rag.ts +274 -0
  200. package/src/web/src/sessions.ts +469 -0
  201. package/src/web/src/shell.ts +804 -0
  202. package/src/web/src/status.ts +300 -0
  203. package/src/web/src/ui-catalog.ts +92 -0
  204. package/src/web/sw.js +71 -0
  205. package/src/web/vendor/README.md +61 -0
  206. package/src/web/vendor/hljs-LICENSE +29 -0
  207. package/src/web/vendor/hljs-theme.css +59 -0
  208. package/src/web/vendor/hljs.js +26 -0
  209. package/src/web-tools.ts +54 -2
  210. package/src/weights.ts +82 -19
  211. package/src/model/fused-decode-kernel.ts +0 -312
  212. package/src/model/fused-geglu-kernel.ts +0 -198
  213. package/src/model/fused-mlp-kernel.ts +0 -169
  214. package/src/model/fused-swiglu-kernel.ts +0 -240
  215. package/src/model/steel-linear-kernel.ts +0 -96
  216. package/src/status-page.html +0 -497
@@ -1,7 +1,7 @@
1
1
  // UniversalDense — the Tier-0 config-driven dense llama-family module
2
2
  // (docs/design/generic-model-support.md §3.1). One module, shaped exactly
3
3
  // like qwen3.ts, with every arch delta selected by the UniversalArgs
4
- // descriptor (archs.ts). Monolith path only: no perf kernels, no compiled
4
+ // descriptor (archs.ts). Monolith path only: no compiled
5
5
  // decode, no kv-quant — slow, never broken. The bar is L1 bit-exactness
6
6
  // vs mlx-lm on this machine's GPU (tests/universal-parity.test.ts).
7
7
  //
@@ -28,6 +28,7 @@ import {
28
28
  type Mask,
29
29
  } from "../gemma4-base";
30
30
  import { genericArgsFor, type UniversalArgs } from "./archs";
31
+ import { compiledSwiglu } from "../minicpm5";
31
32
  import { initializeRope, NoRope, phi3SuRope, UniversalRope } from "./rope";
32
33
  import {
33
34
  loadEmbedding,
@@ -140,8 +141,18 @@ class UniversalAttention {
140
141
  k = disposing(k, ops.transposeAxes(k, [0, 2, 1, 3]));
141
142
  v = disposing(v, ops.transposeAxes(v, [0, 2, 1, 3]));
142
143
 
143
- q = disposing(q, this.rope.apply(q, cache.offset));
144
- k = disposing(k, this.rope.apply(k, cache.offset));
144
+ // Batched decode: the scheduler's mask wrapper exposes each row's REAL
145
+ // position as ropeOffsetArr (rows have different prompt lengths under
146
+ // left-padding; the scalar offset would mis-position every padded row by
147
+ // its pad amount — the 2026-07-03 uneven-batch bug). Serial lane: scalar
148
+ // offset, byte-identical to before (the S1b.1 pattern, cf. qwen3_5.ts).
149
+ const offArr = (cache as { ropeOffsetArr?: MlxArray }).ropeOffsetArr;
150
+ q = disposing(q, offArr
151
+ ? this.rope.applyDynamic(q, offArr)
152
+ : this.rope.apply(q, cache.offset));
153
+ k = disposing(k, offArr
154
+ ? this.rope.applyDynamic(k, offArr)
155
+ : this.rope.apply(k, cache.offset));
145
156
 
146
157
  const [keys, values] = cache.updateAndFetch(k, v);
147
158
  k.dispose();
@@ -215,11 +226,23 @@ class UniversalAttention {
215
226
  }
216
227
  }
217
228
 
218
- class UniversalMLP {
229
+ /** swiglu(gate, up) = nn.silu(gate) * up as ONE call — the whole activation,
230
+ * not just silu. Defaults to the `@mx.compile` version (compiledSwiglu) — the
231
+ * same libmlx kernel mlx-lm dispatches (L1-faithful), bit-identical to the old
232
+ * uncompiled composition but one dispatch instead of three. Set to null to force
233
+ * the uncompiled path (the slower same-parity opt-in). */
234
+ export type SwigluFn = (gate: MlxArray, up: MlxArray) => MlxArray;
235
+
236
+ export class UniversalMLP {
219
237
  readonly gate: AnyLinear | null;
220
238
  readonly up: AnyLinear | null;
221
239
  readonly gateUp: AnyLinear | null;
222
240
  readonly down: AnyLinear;
241
+ /** Fused swiglu (silu(gate)*up), default = the oracle's `@mx.compile` closure.
242
+ * Only consumed for the swiglu / fused_swiglu kinds; geglu/geglu_approx/gelu_mlp
243
+ * are unaffected (the oracle does NOT @mx.compile those — gemma/gemma2/starcoder2
244
+ * use a plain nn.gelu composition). Set to null for the uncompiled composition. */
245
+ swigluFn: SwigluFn | null = compiledSwiglu;
223
246
 
224
247
  constructor(
225
248
  weights: Weights,
@@ -268,6 +291,18 @@ class UniversalMLP {
268
291
  up = this.up!.forward(x);
269
292
  }
270
293
 
294
+ // swiglu / fused_swiglu run silu(gate)*up as ONE fused call (mlx-lm's
295
+ // `@mx.compile swiglu`), matching the oracle kernel set exactly. This is the
296
+ // default (swigluFn = compiledSwiglu); null forces the uncompiled composition.
297
+ if (this.swigluFn && (this.kind === "swiglu" || this.kind === "fused_swiglu")) {
298
+ const hidden = this.swigluFn(gate, up);
299
+ gate.dispose();
300
+ up.dispose();
301
+ const out = this.down.forward(hidden);
302
+ hidden.dispose();
303
+ return out;
304
+ }
305
+
271
306
  let act: MlxArray;
272
307
  if (this.kind === "geglu") {
273
308
  act = ops.geluPrecise(gate); // gemma-1: nn.gelu (erf)
@@ -289,7 +324,7 @@ class UniversalMLP {
289
324
  }
290
325
  }
291
326
 
292
- class UniversalLayer {
327
+ export class UniversalLayer {
293
328
  readonly attn: UniversalAttention;
294
329
  readonly mlp: UniversalMLP;
295
330
  /** pre/gemma2/glm4: input_layernorm. post (olmo2): unused (null). */
@@ -49,32 +49,44 @@ export class UniversalRope {
49
49
  ) {}
50
50
 
51
51
  apply(x: MlxArray, offset: number): MlxArray {
52
- let input = x;
53
- let scaled: MlxArray | null = null;
54
- if (this.preScaleAlways || this.preScale !== 1.0) {
55
- // x[..., :dims] = preScale * x[..., :dims] (weak-scalar mul)
56
- const D = x.shape[x.shape.length - 1]!;
57
- if (this.dims === D) {
58
- scaled = ops.mulScalar(x, this.preScale);
59
- } else {
60
- const start = x.shape.map(() => 0);
61
- const stop = [...x.shape];
62
- stop[stop.length - 1] = this.dims;
63
- const head = x.slice(start, stop);
64
- const headScaled = ops.mulScalar(head, this.preScale);
65
- head.dispose();
66
- scaled = ops.sliceUpdate(x, headScaled, start, stop);
67
- headScaled.dispose();
68
- }
69
- input = scaled;
70
- }
52
+ const input = this.#preScaled(x);
71
53
  const out = ops.ropeScaled(
72
- input, this.dims, this.traditional, this.base, this.scale, offset, this.freqs,
54
+ input ?? x, this.dims, this.traditional, this.base, this.scale, offset, this.freqs,
73
55
  );
74
- scaled?.dispose();
56
+ input?.dispose();
75
57
  return out;
76
58
  }
77
59
 
60
+ /** Per-row array-offset variant (batched decode under left-padding: each
61
+ * row's REAL position rides an int32 [B] array — the scalar offset would
62
+ * mis-position every padded row by its pad amount). Same math as
63
+ * `apply`; only the offset spelling differs. */
64
+ applyDynamic(x: MlxArray, offsetArr: MlxArray): MlxArray {
65
+ const input = this.#preScaled(x);
66
+ const out = ops.ropeScaledDynamic(
67
+ input ?? x, this.dims, this.traditional, this.base, this.scale, offsetArr, this.freqs,
68
+ );
69
+ input?.dispose();
70
+ return out;
71
+ }
72
+
73
+ /** x[..., :dims] = preScale * x[..., :dims] (weak-scalar mul); null when
74
+ * no pre-scale applies (caller uses x unchanged). */
75
+ #preScaled(x: MlxArray): MlxArray | null {
76
+ if (!(this.preScaleAlways || this.preScale !== 1.0)) return null;
77
+ const D = x.shape[x.shape.length - 1]!;
78
+ if (this.dims === D) return ops.mulScalar(x, this.preScale);
79
+ const start = x.shape.map(() => 0);
80
+ const stop = [...x.shape];
81
+ stop[stop.length - 1] = this.dims;
82
+ const head = x.slice(start, stop);
83
+ const headScaled = ops.mulScalar(head, this.preScale);
84
+ head.dispose();
85
+ const scaled = ops.sliceUpdate(x, headScaled, start, stop);
86
+ headScaled.dispose();
87
+ return scaled;
88
+ }
89
+
78
90
  dispose(): void {
79
91
  this.freqs?.dispose();
80
92
  }
@@ -89,6 +101,9 @@ export class NoRope extends UniversalRope {
89
101
  // Return an owned copy-view so callers can dispose uniformly.
90
102
  return x.slice(x.shape.map(() => 0), [...x.shape]);
91
103
  }
104
+ override applyDynamic(x: MlxArray, _offsetArr: MlxArray): MlxArray {
105
+ return x.slice(x.shape.map(() => 0), [...x.shape]);
106
+ }
92
107
  }
93
108
 
94
109
  /** `base ** (arange(0, dims, 2) / dims)` with the oracle's dtypes:
@@ -0,0 +1,523 @@
1
+ #include <errno.h>
2
+ #include <fcntl.h>
3
+ #include <limits.h>
4
+ #include <pthread.h>
5
+ #include <stdint.h>
6
+ #include <stdlib.h>
7
+ #include <string.h>
8
+ #include <sys/mman.h>
9
+ #include <sys/resource.h>
10
+ #include <unistd.h>
11
+ #ifdef __APPLE__
12
+ #include <mach/mach.h>
13
+ #endif
14
+
15
+ typedef struct expert_io_pool expert_io_pool;
16
+ #define EXPERT_IO_MAX_SEGMENTS 8
17
+ #define EXPERT_IO_HINT_QUEUE_CAP 4096
18
+ #define EXPERT_IO_HINT_STATS 8
19
+ typedef struct {
20
+ uint32_t slot, segment_count;
21
+ uint64_t generation;
22
+ uint32_t file_index[EXPERT_IO_MAX_SEGMENTS];
23
+ uint64_t offset[EXPERT_IO_MAX_SEGMENTS];
24
+ uint64_t destination[EXPERT_IO_MAX_SEGMENTS];
25
+ uint64_t length[EXPERT_IO_MAX_SEGMENTS];
26
+ } io_job;
27
+ typedef struct {
28
+ uint32_t segment_count;
29
+ uint32_t file_index[EXPERT_IO_MAX_SEGMENTS];
30
+ uint64_t offset[EXPERT_IO_MAX_SEGMENTS];
31
+ uint64_t length[EXPERT_IO_MAX_SEGMENTS];
32
+ } io_hint_job;
33
+ typedef struct {
34
+ void *data;
35
+ uint64_t generation, capacity, length;
36
+ int state, error, cancelled, gpu_lease, wired;
37
+ } io_slot;
38
+
39
+ struct expert_io_pool {
40
+ int stopping;
41
+ int wire_slots;
42
+ uint32_t file_count, slot_count, worker_count, qcap, qhead, qtail, qlen;
43
+ uint32_t hint_qcap, hint_qhead, hint_qtail, hint_qlen, hint_inflight;
44
+ int mu_init, work_init, changed_init, hint_work_init, hint_worker_started;
45
+ int *fds;
46
+ int *hint_fds;
47
+ io_slot *slots;
48
+ io_job *queue;
49
+ io_hint_job *hint_queue;
50
+ pthread_t *workers;
51
+ pthread_t hint_worker;
52
+ pthread_mutex_t mu;
53
+ pthread_cond_t work, changed, hint_work;
54
+ uint64_t hint_submitted, hint_completed, hint_dropped;
55
+ uint64_t hint_operations, hint_bytes, hint_errors;
56
+ };
57
+
58
+ enum { SLOT_IDLE = 0, SLOT_LOADING = 1, SLOT_READY = 2, SLOT_LEASED = 3 };
59
+
60
+ static int pread_full(int fd, void *dst, uint64_t length, uint64_t offset) {
61
+ uint8_t *p = dst;
62
+ while (length) {
63
+ size_t ask = length > (uint64_t)SSIZE_MAX ? (size_t)SSIZE_MAX : (size_t)length;
64
+ ssize_t n = pread(fd, p, ask, (off_t)offset);
65
+ if (n < 0 && errno == EINTR) continue;
66
+ if (n < 0) return errno;
67
+ if (n == 0) return ENODATA;
68
+ p += n; offset += (uint64_t)n; length -= (uint64_t)n;
69
+ }
70
+ return 0;
71
+ }
72
+
73
+ static void *worker_main(void *arg) {
74
+ expert_io_pool *p = arg;
75
+ for (;;) {
76
+ pthread_mutex_lock(&p->mu);
77
+ while (!p->stopping && p->qlen == 0) pthread_cond_wait(&p->work, &p->mu);
78
+ if (p->stopping && p->qlen == 0) { pthread_mutex_unlock(&p->mu); return NULL; }
79
+ io_job job = p->queue[p->qhead];
80
+ p->qhead = (p->qhead + 1) % p->qcap; p->qlen--;
81
+ io_slot *slot = &p->slots[job.slot];
82
+ pthread_mutex_unlock(&p->mu);
83
+ int error = 0;
84
+ uint64_t extent = 0;
85
+ for (uint32_t i = 0; i < job.segment_count && !error; i++) {
86
+ error = pread_full(
87
+ p->fds[job.file_index[i]],
88
+ (uint8_t *)slot->data + job.destination[i],
89
+ job.length[i],
90
+ job.offset[i]
91
+ );
92
+ uint64_t end = job.destination[i] + job.length[i];
93
+ if (end > extent) extent = end;
94
+ }
95
+ pthread_mutex_lock(&p->mu);
96
+ if (slot->generation == job.generation && slot->state == SLOT_LOADING) {
97
+ slot->error = slot->cancelled ? ECANCELED : error;
98
+ slot->length = slot->error ? 0 : extent; slot->state = SLOT_READY;
99
+ }
100
+ pthread_cond_broadcast(&p->changed);
101
+ pthread_mutex_unlock(&p->mu);
102
+ }
103
+ }
104
+
105
+ static int advise_willneed(int fd, uint64_t offset, uint64_t length) {
106
+ #ifdef __APPLE__
107
+ while (length) {
108
+ int chunk = length > (uint64_t)INT_MAX ? INT_MAX : (int)length;
109
+ struct radvisory advice;
110
+ advice.ra_offset = (off_t)offset;
111
+ advice.ra_count = chunk;
112
+ if (fcntl(fd, F_RDADVISE, &advice) != 0) return errno;
113
+ offset += (uint64_t)chunk;
114
+ length -= (uint64_t)chunk;
115
+ }
116
+ return 0;
117
+ #elif defined(POSIX_FADV_WILLNEED)
118
+ int status = posix_fadvise(fd, (off_t)offset, (off_t)length,
119
+ POSIX_FADV_WILLNEED);
120
+ return status;
121
+ #else
122
+ (void)fd; (void)offset; (void)length;
123
+ return 0;
124
+ #endif
125
+ }
126
+
127
+ static void *hint_worker_main(void *arg) {
128
+ expert_io_pool *p = arg;
129
+ for (;;) {
130
+ pthread_mutex_lock(&p->mu);
131
+ while (!p->stopping && p->hint_qlen == 0)
132
+ pthread_cond_wait(&p->hint_work, &p->mu);
133
+ if (p->stopping) { pthread_mutex_unlock(&p->mu); return NULL; }
134
+ io_hint_job job = p->hint_queue[p->hint_qhead];
135
+ p->hint_qhead = (p->hint_qhead + 1) % p->hint_qcap;
136
+ p->hint_qlen--;
137
+ p->hint_inflight = 1;
138
+ pthread_mutex_unlock(&p->mu);
139
+
140
+ int error = 0;
141
+ for (uint32_t i = 0; i < job.segment_count && !error; i++) {
142
+ error = advise_willneed(
143
+ p->hint_fds[job.file_index[i]], job.offset[i], job.length[i]
144
+ );
145
+ }
146
+
147
+ pthread_mutex_lock(&p->mu);
148
+ p->hint_completed++;
149
+ if (error) p->hint_errors++;
150
+ p->hint_inflight = 0;
151
+ pthread_cond_broadcast(&p->changed);
152
+ pthread_mutex_unlock(&p->mu);
153
+ }
154
+ }
155
+
156
+ static expert_io_pool *open_pool(const char *const *paths, uint32_t file_count,
157
+ uint32_t slots, uint64_t capacity,
158
+ uint64_t alignment, uint32_t workers, int no_cache) {
159
+ if (!paths || !file_count || !slots || slots > UINT32_MAX / 2 || !capacity ||
160
+ !workers || workers > 64 || alignment < sizeof(void *) ||
161
+ (alignment & (alignment - 1))) { errno = EINVAL; return NULL; }
162
+ expert_io_pool *p = calloc(1, sizeof(*p));
163
+ if (!p) return NULL;
164
+ p->file_count = file_count;
165
+ p->fds = malloc((size_t)file_count * sizeof(*p->fds));
166
+ p->hint_fds = malloc((size_t)file_count * sizeof(*p->hint_fds));
167
+ if (!p->fds || !p->hint_fds) {
168
+ free(p->hint_fds); free(p->fds); free(p); return NULL;
169
+ }
170
+ for (uint32_t i = 0; i < file_count; i++) {
171
+ p->fds[i] = -1;
172
+ p->hint_fds[i] = -1;
173
+ }
174
+ for (uint32_t i = 0; i < file_count; i++) {
175
+ if (!paths[i] || !paths[i][0]) { errno = EINVAL; goto fail; }
176
+ p->fds[i] = open(paths[i], O_RDONLY);
177
+ if (p->fds[i] < 0) goto fail;
178
+ p->hint_fds[i] = open(paths[i], O_RDONLY);
179
+ if (p->hint_fds[i] < 0) goto fail;
180
+ #ifdef F_NOCACHE
181
+ if (no_cache && fcntl(p->fds[i], F_NOCACHE, 1) != 0) goto fail;
182
+ #else
183
+ (void)no_cache;
184
+ #endif
185
+ }
186
+ p->slot_count = slots; p->qcap = slots * 2;
187
+ p->hint_qcap = EXPERT_IO_HINT_QUEUE_CAP;
188
+ p->slots = calloc(slots, sizeof(*p->slots)); p->queue = calloc(p->qcap, sizeof(*p->queue));
189
+ p->hint_queue = calloc(p->hint_qcap, sizeof(*p->hint_queue));
190
+ p->workers = calloc(workers, sizeof(*p->workers));
191
+ if (!p->slots || !p->queue || !p->hint_queue || !p->workers) goto fail;
192
+ if (pthread_mutex_init(&p->mu, NULL)) goto fail; p->mu_init = 1;
193
+ if (pthread_cond_init(&p->work, NULL)) goto fail_started; p->work_init = 1;
194
+ if (pthread_cond_init(&p->changed, NULL)) goto fail_started; p->changed_init = 1;
195
+ if (pthread_cond_init(&p->hint_work, NULL)) goto fail_started; p->hint_work_init = 1;
196
+ for (uint32_t i = 0; i < slots; i++) {
197
+ if (posix_memalign(&p->slots[i].data, (size_t)alignment, (size_t)capacity)) goto fail_started;
198
+ p->slots[i].capacity = capacity;
199
+ }
200
+ for (uint32_t i = 0; i < workers; i++) {
201
+ if (pthread_create(&p->workers[i], NULL, worker_main, p)) goto fail_started;
202
+ p->worker_count++;
203
+ }
204
+ if (pthread_create(&p->hint_worker, NULL, hint_worker_main, p))
205
+ goto fail_started;
206
+ p->hint_worker_started = 1;
207
+ return p;
208
+ fail_started:
209
+ if (p->mu_init) {
210
+ pthread_mutex_lock(&p->mu); p->stopping = 1;
211
+ if (p->work_init) pthread_cond_broadcast(&p->work);
212
+ if (p->hint_work_init) pthread_cond_broadcast(&p->hint_work);
213
+ pthread_mutex_unlock(&p->mu);
214
+ }
215
+ for (uint32_t i = 0; i < p->worker_count; i++) pthread_join(p->workers[i], NULL);
216
+ if (p->hint_worker_started) pthread_join(p->hint_worker, NULL);
217
+ if (p->hint_work_init) pthread_cond_destroy(&p->hint_work);
218
+ if (p->changed_init) pthread_cond_destroy(&p->changed);
219
+ if (p->work_init) pthread_cond_destroy(&p->work);
220
+ if (p->mu_init) pthread_mutex_destroy(&p->mu);
221
+ fail:
222
+ if (p->slots) for (uint32_t i = 0; i < slots; i++) free(p->slots[i].data);
223
+ free(p->workers); free(p->hint_queue); free(p->queue); free(p->slots);
224
+ if (p->fds) for (uint32_t i = 0; i < file_count; i++) if (p->fds[i] >= 0) close(p->fds[i]);
225
+ if (p->hint_fds) for (uint32_t i = 0; i < file_count; i++) if (p->hint_fds[i] >= 0) close(p->hint_fds[i]);
226
+ free(p->hint_fds); free(p->fds); free(p); return NULL;
227
+ }
228
+
229
+ expert_io_pool *mlx_bun_expert_io_open(const char *path, uint32_t slots, uint64_t capacity,
230
+ uint64_t alignment, uint32_t workers, int no_cache) {
231
+ const char *paths[1] = {path};
232
+ return open_pool(paths, 1, slots, capacity, alignment, workers, no_cache);
233
+ }
234
+
235
+ expert_io_pool *mlx_bun_expert_io_open_many(const char *path_blob, uint64_t path_blob_length,
236
+ uint32_t file_count, uint32_t slots,
237
+ uint64_t capacity, uint64_t alignment,
238
+ uint32_t workers, int no_cache) {
239
+ if (!path_blob || !path_blob_length || !file_count ||
240
+ path_blob_length > (uint64_t)SIZE_MAX) { errno = EINVAL; return NULL; }
241
+ const char **paths = calloc(file_count, sizeof(*paths));
242
+ if (!paths) return NULL;
243
+ uint64_t cursor = 0;
244
+ int valid = 1;
245
+ for (uint32_t i = 0; i < file_count; i++) {
246
+ if (cursor >= path_blob_length) { valid = 0; break; }
247
+ paths[i] = path_blob + cursor;
248
+ size_t remaining = (size_t)(path_blob_length - cursor);
249
+ size_t length = strnlen(paths[i], remaining);
250
+ if (length == 0 || length == remaining) { valid = 0; break; }
251
+ cursor += (uint64_t)length + 1;
252
+ }
253
+ if (cursor != path_blob_length) valid = 0;
254
+ expert_io_pool *pool = valid
255
+ ? open_pool(paths, file_count, slots, capacity, alignment, workers, no_cache)
256
+ : NULL;
257
+ free(paths);
258
+ if (!valid) errno = EINVAL;
259
+ return pool;
260
+ }
261
+
262
+ int mlx_bun_expert_io_submitv(expert_io_pool *p, uint32_t slot_id, uint64_t generation,
263
+ const uint32_t *file_index, const uint64_t *offset,
264
+ const uint64_t *destination, const uint64_t *length,
265
+ uint32_t segment_count) {
266
+ if (!p || slot_id >= p->slot_count || !generation || !file_index || !offset ||
267
+ !destination || !length || !segment_count ||
268
+ segment_count > EXPERT_IO_MAX_SEGMENTS) return EINVAL;
269
+ uint64_t extent = 0;
270
+ for (uint32_t i = 0; i < segment_count; i++) {
271
+ if (file_index[i] >= p->file_count || !length[i]) return EINVAL;
272
+ if (offset[i] > INT64_MAX || length[i] > (uint64_t)INT64_MAX - offset[i])
273
+ return EOVERFLOW;
274
+ if (destination[i] > p->slots[slot_id].capacity ||
275
+ length[i] > p->slots[slot_id].capacity - destination[i]) return EBUSY;
276
+ uint64_t end = destination[i] + length[i];
277
+ if (end > extent) extent = end;
278
+ for (uint32_t prior = 0; prior < i; prior++) {
279
+ uint64_t prior_end = destination[prior] + length[prior];
280
+ if (destination[i] < prior_end && destination[prior] < end) return EINVAL;
281
+ }
282
+ }
283
+ pthread_mutex_lock(&p->mu);
284
+ io_slot *slot = &p->slots[slot_id];
285
+ int error = 0;
286
+ if (extent > slot->capacity || slot->state == SLOT_LOADING || slot->state == SLOT_LEASED) error = EBUSY;
287
+ else if (p->qlen == p->qcap) error = EAGAIN;
288
+ else {
289
+ if (p->wire_slots && !slot->wired) {
290
+ if (mlock(slot->data, (size_t)slot->capacity) != 0) error = errno;
291
+ else slot->wired = 1;
292
+ }
293
+ }
294
+ if (!error) {
295
+ slot->generation = generation; slot->length = 0; slot->error = 0; slot->cancelled = 0; slot->state = SLOT_LOADING;
296
+ io_job *job = &p->queue[p->qtail];
297
+ memset(job, 0, sizeof(*job));
298
+ job->slot = slot_id; job->generation = generation; job->segment_count = segment_count;
299
+ for (uint32_t i = 0; i < segment_count; i++) {
300
+ job->file_index[i] = file_index[i];
301
+ job->offset[i] = offset[i];
302
+ job->destination[i] = destination[i];
303
+ job->length[i] = length[i];
304
+ }
305
+ p->qtail = (p->qtail + 1) % p->qcap; p->qlen++;
306
+ pthread_cond_signal(&p->work);
307
+ }
308
+ pthread_mutex_unlock(&p->mu); return error;
309
+ }
310
+
311
+ int mlx_bun_expert_io_hintv(expert_io_pool *p,
312
+ const uint32_t *file_index,
313
+ const uint64_t *offset,
314
+ const uint64_t *length,
315
+ uint32_t segment_count) {
316
+ if (!p || !file_index || !offset || !length || !segment_count ||
317
+ segment_count > EXPERT_IO_MAX_SEGMENTS) return EINVAL;
318
+ uint64_t bytes = 0;
319
+ for (uint32_t i = 0; i < segment_count; i++) {
320
+ if (file_index[i] >= p->file_count || !length[i]) return EINVAL;
321
+ if (offset[i] > INT64_MAX || length[i] > (uint64_t)INT64_MAX - offset[i])
322
+ return EOVERFLOW;
323
+ if (bytes > UINT64_MAX - length[i]) return EOVERFLOW;
324
+ bytes += length[i];
325
+ }
326
+
327
+ pthread_mutex_lock(&p->mu);
328
+ int error = 0;
329
+ if (p->stopping) error = ECANCELED;
330
+ else if (p->hint_qlen == p->hint_qcap) {
331
+ p->hint_dropped++;
332
+ error = EAGAIN;
333
+ } else {
334
+ io_hint_job *job = &p->hint_queue[p->hint_qtail];
335
+ memset(job, 0, sizeof(*job));
336
+ job->segment_count = segment_count;
337
+ for (uint32_t i = 0; i < segment_count; i++) {
338
+ job->file_index[i] = file_index[i];
339
+ job->offset[i] = offset[i];
340
+ job->length[i] = length[i];
341
+ }
342
+ p->hint_qtail = (p->hint_qtail + 1) % p->hint_qcap;
343
+ p->hint_qlen++;
344
+ p->hint_submitted++;
345
+ p->hint_operations += segment_count;
346
+ p->hint_bytes += bytes;
347
+ pthread_cond_signal(&p->hint_work);
348
+ }
349
+ pthread_mutex_unlock(&p->mu);
350
+ return error;
351
+ }
352
+
353
+ int mlx_bun_expert_io_hint_stats(expert_io_pool *p, uint64_t *out,
354
+ uint32_t count) {
355
+ if (!p || !out || count < EXPERT_IO_HINT_STATS) return EINVAL;
356
+ pthread_mutex_lock(&p->mu);
357
+ out[0] = p->hint_submitted;
358
+ out[1] = p->hint_completed;
359
+ out[2] = p->hint_dropped;
360
+ out[3] = p->hint_operations;
361
+ out[4] = p->hint_bytes;
362
+ out[5] = p->hint_errors;
363
+ out[6] = p->hint_qlen;
364
+ out[7] = p->hint_inflight;
365
+ pthread_mutex_unlock(&p->mu);
366
+ return 0;
367
+ }
368
+
369
+ int mlx_bun_expert_io_set_wiring(expert_io_pool *p, int enabled) {
370
+ if (!p) return EINVAL;
371
+ pthread_mutex_lock(&p->mu);
372
+ int error = 0;
373
+ if (p->qlen != 0) error = EBUSY;
374
+ for (uint32_t i = 0; i < p->slot_count && !error; i++) {
375
+ if (p->slots[i].state == SLOT_LOADING ||
376
+ p->slots[i].state == SLOT_LEASED) error = EBUSY;
377
+ }
378
+ if (!error) p->wire_slots = enabled != 0;
379
+ pthread_mutex_unlock(&p->mu);
380
+ return error;
381
+ }
382
+
383
+ int mlx_bun_expert_io_submit(expert_io_pool *p, uint32_t slot_id, uint64_t generation,
384
+ uint64_t offset, uint64_t length) {
385
+ const uint32_t file_index[1] = {0};
386
+ const uint64_t destination[1] = {0};
387
+ return mlx_bun_expert_io_submitv(
388
+ p, slot_id, generation, file_index, &offset, destination, &length, 1
389
+ );
390
+ }
391
+
392
+ int mlx_bun_expert_io_cancel(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
393
+ if (!p || slot_id >= p->slot_count) return EINVAL;
394
+ pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
395
+ int error = s->generation != generation ? ESTALE : 0;
396
+ if (!error && s->state == SLOT_LOADING) s->cancelled = 1;
397
+ else if (!error && s->state == SLOT_READY) { s->error = ECANCELED; s->length = 0; }
398
+ else if (!error) error = EBUSY;
399
+ pthread_mutex_unlock(&p->mu); return error;
400
+ }
401
+
402
+ int mlx_bun_expert_io_wait(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
403
+ if (!p || slot_id >= p->slot_count) return EINVAL;
404
+ pthread_mutex_lock(&p->mu); io_slot *slot = &p->slots[slot_id];
405
+ while (slot->generation == generation && slot->state == SLOT_LOADING) pthread_cond_wait(&p->changed, &p->mu);
406
+ int error = slot->generation != generation ? ESTALE : slot->error;
407
+ pthread_mutex_unlock(&p->mu); return error;
408
+ }
409
+
410
+ int mlx_bun_expert_io_poll(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
411
+ if (!p || slot_id >= p->slot_count) return EINVAL;
412
+ pthread_mutex_lock(&p->mu); io_slot *slot = &p->slots[slot_id];
413
+ int result = slot->generation != generation ? ESTALE : slot->state == SLOT_LOADING ? EAGAIN : slot->error;
414
+ pthread_mutex_unlock(&p->mu); return result;
415
+ }
416
+
417
+ int mlx_bun_expert_io_lease(expert_io_pool *p, uint32_t slot_id, uint64_t generation, int gpu) {
418
+ if (!p || slot_id >= p->slot_count) return EINVAL;
419
+ pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
420
+ int error = s->generation != generation ? ESTALE : s->state != SLOT_READY || s->error ? EBUSY : 0;
421
+ if (!error) { s->state = SLOT_LEASED; s->gpu_lease = gpu != 0; }
422
+ pthread_mutex_unlock(&p->mu); return error;
423
+ }
424
+
425
+ int mlx_bun_expert_io_release(expert_io_pool *p, uint32_t slot_id, uint64_t generation, int gpu_fenced) {
426
+ if (!p || slot_id >= p->slot_count) return EINVAL;
427
+ pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
428
+ int error = s->generation != generation ? ESTALE : s->state != SLOT_LEASED ? EINVAL :
429
+ s->gpu_lease && !gpu_fenced ? EBUSY : 0;
430
+ if (!error) { s->state = SLOT_READY; s->gpu_lease = 0; }
431
+ pthread_mutex_unlock(&p->mu); return error;
432
+ }
433
+
434
+ int mlx_bun_expert_io_discard(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
435
+ if (!p || slot_id >= p->slot_count || !generation) return EINVAL;
436
+ pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
437
+ int error = s->generation != generation ? ESTALE :
438
+ s->state != SLOT_READY ? EBUSY : 0;
439
+ if (!error && s->wired) {
440
+ if (munlock(s->data, (size_t)s->capacity) != 0) error = errno;
441
+ else s->wired = 0;
442
+ }
443
+ if (!error && madvise(s->data, (size_t)s->capacity, MADV_DONTNEED) != 0)
444
+ error = errno;
445
+ if (!error) {
446
+ s->state = SLOT_IDLE;
447
+ s->length = 0;
448
+ s->error = 0;
449
+ s->cancelled = 0;
450
+ s->gpu_lease = 0;
451
+ }
452
+ pthread_mutex_unlock(&p->mu); return error;
453
+ }
454
+
455
+ uint64_t mlx_bun_process_phys_footprint(void) {
456
+ #ifdef __APPLE__
457
+ task_vm_info_data_t info;
458
+ mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
459
+ kern_return_t status = task_info(
460
+ mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count
461
+ );
462
+ if (status == KERN_SUCCESS) return (uint64_t)info.phys_footprint;
463
+ #endif
464
+ struct rusage usage;
465
+ if (getrusage(RUSAGE_SELF, &usage) != 0) return 0;
466
+ #ifdef __APPLE__
467
+ return (uint64_t)usage.ru_maxrss;
468
+ #else
469
+ return (uint64_t)usage.ru_maxrss * 1024;
470
+ #endif
471
+ }
472
+
473
+ uint64_t mlx_bun_process_compressed(void) {
474
+ #ifdef __APPLE__
475
+ task_vm_info_data_t info;
476
+ mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
477
+ kern_return_t status = task_info(
478
+ mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count
479
+ );
480
+ if (status == KERN_SUCCESS) return (uint64_t)info.compressed;
481
+ #endif
482
+ return 0;
483
+ }
484
+
485
+ void *mlx_bun_expert_io_ptr(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
486
+ if (!p || slot_id >= p->slot_count) return NULL;
487
+ pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
488
+ void *result = s->state == SLOT_LEASED && s->generation == generation ? s->data : NULL;
489
+ pthread_mutex_unlock(&p->mu); return result;
490
+ }
491
+ uint64_t mlx_bun_expert_io_length(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
492
+ if (!p || slot_id >= p->slot_count) return 0;
493
+ pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
494
+ uint64_t result = s->state == SLOT_LEASED && s->generation == generation ? s->length : 0;
495
+ pthread_mutex_unlock(&p->mu); return result;
496
+ }
497
+
498
+ int mlx_bun_expert_io_close(expert_io_pool *p) {
499
+ if (!p) return 0;
500
+ pthread_mutex_lock(&p->mu);
501
+ for (uint32_t i = 0; i < p->slot_count; i++) if (p->slots[i].state == SLOT_LEASED) {
502
+ pthread_mutex_unlock(&p->mu); return EBUSY;
503
+ }
504
+ pthread_mutex_unlock(&p->mu);
505
+ pthread_mutex_lock(&p->mu);
506
+ p->stopping = 1;
507
+ pthread_cond_broadcast(&p->work);
508
+ pthread_cond_broadcast(&p->hint_work);
509
+ pthread_mutex_unlock(&p->mu);
510
+ for (uint32_t i = 0; i < p->worker_count; i++) pthread_join(p->workers[i], NULL);
511
+ if (p->hint_worker_started) pthread_join(p->hint_worker, NULL);
512
+ for (uint32_t i = 0; i < p->slot_count; i++) {
513
+ if (p->slots[i].wired)
514
+ (void)munlock(p->slots[i].data, (size_t)p->slots[i].capacity);
515
+ free(p->slots[i].data);
516
+ }
517
+ pthread_cond_destroy(&p->hint_work); pthread_cond_destroy(&p->changed);
518
+ pthread_cond_destroy(&p->work); pthread_mutex_destroy(&p->mu);
519
+ for (uint32_t i = 0; i < p->file_count; i++) close(p->fds[i]);
520
+ for (uint32_t i = 0; i < p->file_count; i++) close(p->hint_fds[i]);
521
+ free(p->hint_fds); free(p->fds); free(p->workers); free(p->hint_queue);
522
+ free(p->queue); free(p->slots); free(p); return 0;
523
+ }