Frontier · Bun Blog
Riscrivere Bun in Rust
Jarred Sumner spiega perché e come il bundler di Bun è stato riscritto da Zig a Rust, compresi i cicli di codifica degli agenti, la revisione contraddittoria, i test e il lavoro sulle prestazioni dietro la migrazione.

Questo articolo è tradotto dal testo originale inglese.
Edizione testuale: grafici di dati, visualizzazioni interattive e diagrammi di flusso della fonte non sono riprodotti qui. Consulta l’originale per la grafica completa.
Informativa: Bun è stata acquisita da Anthropic nel dicembre 2025. Io e altri membri del team Bun lavoriamo presso Anthropic. Ho utilizzato una versione pre-release di Claude Fable 5 per gran parte della riscrittura di Rust.
Bun è iniziato come port linea per linea del transpiler JavaScript e TypeScript di esbuild da Go a Zig. Ho scritto la mia prima riga di Zig il 16 aprile 2021. Scommetto su Zig dopo aver visto il Zig Language Reference a pagina singola su Hacker News e dopo essere rimasto davvero entusiasta del controllo di basso livello e dell'attenzione alle prestazioni.
Fin dall'inizio, l'ambito di Bun era enorme:
- JavaScript, TypeScript e transpiler, minimizzatore e bundler CSS
- Gestore pacchetti compatibile con npm
- Jest-come test runner
- Risoluzione del modulo compatibile con Node.js e TypeScript
- Client HTTP/1.1 e WebSocket
- Implementazioni API Node.js come
fs,net,tlse decine di altri moduli
La versione iniziale di Bun è stata scritta da me in 1 anno, in un angusto appartamento di Oakland, prima di LLM, in Zig. Il risultato predefinito per progetti dall'ambito ambizioso come Bun è unirsi al cimitero dei progetti morti su una pagina del profilo GitHub. Zig ha reso Bun possibile. Non sarei mai stato in grado di costruire così tanto in 1 anno se non fosse stato per Zig.
Oggi, la CLI di Bun ottiene oltre 22 milioni di download mensili. Strumenti popolari come Claude Code e OpenCode scommettono su Bun come runtime. Vercel, Railway, DigitalOcean e altri offrono supporto di terze parti per Bun.
Bun ha rappresentato anche una sfida per la stabilità. Ecco un piccolo esempio di bug che abbiamo corretto nella Bun v1.3.14:
- arresto anomalo dell'heap-use-after-free in
node:zlibquando si chiama.reset()su un flusso zlib, Brotli o Zstd mentre un.write()asincrono è ancora in corso sul threadpool - arresto anomalo use-after-free in
node:zlibquando un callbackonerrorha emesso un rientrantewrite()seguito daclose()su handle nativi - use-after-free si arresta in modo anomalo in
node:http2quando i callback JS rientranti (ad esempiosession.request()all'interno di un ascoltatore di timeout, un getter di opzioni o un callback di scrittura) hanno attivato un rehash hashmap, invalidando i puntatori di flusso interni - use-after-free in
UDPSocket.send()esendMany()dove il codice utente nei callbackvalueOf()otoString()potrebbe staccare unArrayBuffertra l'acquisizione del payload e l'effettivo invio - arresto anomalo e lettura fuori dai limiti in
Buffer#copyeBuffer#fillquando un callbackvalueOfstacca o ridimensiona ilArrayBuffersottostante durante la coercizione dell'argomento - scrittura heap fuori limite in
UDPSocket.sendMany()quando lo stato di connessione del socket è cambiato durante l'iterazione tramite callback JS dell'utente - Perdita di memoria in
crypto.scryptdove i buffer di callback e password/salt protetti non sono mai stati rilasciati quando l'allocazione del buffer di output non è riuscita SSLWrapper.initha fatto trapelare la passphrase strdup'd nei percorsi di errore- perdita di memoria in
tlsSocket.setSession()dove ogni chiamata perdeva unSSL_SESSION(~6,5 KB per chiamata) a causa di unSSL_SESSION_freemancante dopod2i_SSL_SESSION - Perdita di memoria in cui
fs.watch()watcher non sono mai stati sottoposti a Garbage Collection dopo.close(), causata da un underflow del conteggio dei riferimenti che ha bloccato in modo permanente ogni watcher come root GC - doppio arresto anomalo del parser CSS quando
background-clipaveva prefissi del fornitore e sfondi multistrato DuplexUpgradeContextnon è mai stato liberato: una fuga di notizie completa secondotls.connect({ socket: duplex })- arresto anomalo della race condition in
MessageEventin cui il thread del marcatore GC potrebbe osservare una variante interrotta inm_datadurante l'accesso simultaneo daBroadcastChanneloMessagePort
Avremmo potuto continuare a correggere questo tipo di bug una tantum per sempre, ma lo dobbiamo ai nostri utenti che contano su di noi per fare di meglio e prevenire sistematicamente che questo tipo di bug si ripeta.
Quello che stavamo già facendo
- Abbiamo aggiornato il compilatore Zig per aggiungere il supporto di Address Sanitizer. Eseguiamo la nostra suite di test con ASAN su ogni commit.
- Forniamo Zig build ReleaseSafe con controllo di sicurezza su Windows
- Effettuiamo il fuzz delle API runtime di Bun 24 ore su 24, 7 giorni su 7, utilizzando Fuzzilli, il fuzzer del motore JavaScript utilizzato da V8 e JavaScriptCore
- Abbiamo un sacco di test di perdita di memoria end-to-end
Questo è più di quello che fanno molti progetti.
Basta essere davvero intelligenti e non commettere errori?
Il nostro elenco di correzioni di bug non era soddisfacente ed ero stanco di andare a dormire preoccupandomi dei crash in Bun. Non biasimo Zig per questo: altri utenti di Zig non hanno gli stessi bug che abbiamo avuto noi, e mescolare GC con memoria gestita manualmente è una cosa abbastanza insolita da far sì che il software abbia bisogno che nessun linguaggio lo progetti realmente. Non saremmo arrivati a questo punto se non fosse stato per Zig e te ne sarò sempre grato. Fino a poco tempo fa, la scelta del linguaggio di programmazione era una decisione a senso unico per un progetto come Bun.
JavaScript è un linguaggio di garbage collection e i moderni motori JavaScript come JavaScriptCore (e V8) hanno regole rigide sulla gestione delle eccezioni e sul garbage collector. Zig, come C, non gestisce la memoria per te e questo è un compromesso che per molti progetti è un ottimo motivo per utilizzare Zig. Zig non ha costruttori/distruttori e si prevede che la maggior parte della pulizia venga scritta esplicitamente in ciascun sito di chiamata con defer.
Per Bun, la corretta gestione della durata dei valori raccolti in modo spazzatura e dei valori gestiti manualmente è stata una delle principali fonti di problemi di stabilità: molto spesso piccole perdite di memoria e occasionalmente arresti anomali. Ogni allocazione di memoria deve essere meticolosamente rivista. Dove vengono liberati questi byte? Come possiamo garantire che venga liberato una sola volta? Abbiamo controllato correttamente le eccezioni JavaScript? Questo puntatore di raccolta dati inutili è visibile allo scanner dello stack conservativo? Si tratta di memoria garbage collection o di memoria gestita manualmente?
Per problemi di stabilità, saperlo il prima possibile è la soluzione migliore. Il fuzzing avviene dopo l'unione del codice. La CI avviene quando viene inserito il codice. I controlli di sicurezza del runtime e la pulizia degli indirizzi vengono eseguiti quando il codice viene eseguito (si spera in fase di sviluppo, prima dell'IC).
Un modo comune per ridurre questa classe di problemi è garantire che il codice di pulizia venga sempre eseguito esattamente una volta per il codice che lo richiede. Zig è progettato per essere un linguaggio semplice senza flusso di controllo nascosto, quindi preferisce la parola chiave esplicita defer per eseguire il codice alla fine di un ambito rispetto al ~Destructor implicito di C++ o al Drop implicito di Rust.
| Lingua | Pulizia |
|---|---|
| Zig | defer, errdefer |
| C++ | ~Distruttore, &&Sposta |
| Rust | Rilascia |
Per il codice Zig, quando esattamente dovremmo eseguire il codice di pulizia? Se passiamo lo stesso *T a molte funzioni diverse, come facciamo a sapere quando non è più accessibile e può essere ripulito? Come funziona quando alcune funzioni devono continuare a fare riferimento alla memoria dopo che la funzione è stata chiamata? Il nostro approccio attuale è un mix di:
- durata dell'arena, in cui l'ambito di quando è accessibile è chiaro (lo stato del parser non sfugge alla funzione chiamante e quindi i nodi AST sono una buona scelta lì)
- conteggio dei riferimenti
- presta molta attenzione
Molti progetti scelgono di rispondere a questo tipo di domande attraverso una guida di stile. La TigerStyle di TigerBeetle è un esempio in Zig e la guida allo stile C++ di Google da 31.000 parole è un altro. La sfida con le guide di stile è l’applicazione. Come ti assicuri che la guida di stile venga seguita? Storicamente, la revisione del codice era la risposta con l'applicazione del massimo sforzo tramite linter e analizzatori statici.
Avere una guida di stile rigida con chiare aspettative di proprietà esplicitamente esplicitate nel sistema di tipi era una vera opzione per Bun. Dato che Zig non ha sovraccarico degli operatori, probabilmente ci ritroveremmo con molto codice simile a questo:
fn foo(a_ptr: SharedPtr(TCPSocket)) !void {
const a: *TCPSocket = a_ptr.get();
defer a_ptr.deref();
const b = try do_something_with_a(a);
defer b.deref();
// ...
}
Questo è meno ergonomico di quanto Zig ci aspettiamo:
fn foo(a: *TCPSocket) !void {
const b = try do_something_with_a(a);
// ...
}
Che ne dici di C/C++?
Circa il 20% del codice di Bun è scritto in C++ e Bun incorpora diverse librerie C/C++:
- JavaScriptCore, il motore JavaScript su cui si basa Safari
- uWebSocket e usocket: il nostro server HTTP/WebSocket e loop di eventi
- lshpack e lsquic -
HPACKe librerie HTTP/3 - BoringSSL, il fork OpenSSL di Google
- SQLite
C++ invece di Zig sarebbe una scelta ragionevole per Bun. Otterremmo costruttori e distruttori. Potremmo eliminare moltissimi extern "C" codice wrapper.
Tuttavia, continueremmo a fare affidamento sulle guide di stile applicate tramite la revisione del codice e, anche con ASAN, si verificherebbero comunque danni alla memoria e perdite di memoria.
Perché Rust?
Una grande percentuale di bug di quell'elenco sono use-after-free, double-free e "forgot to free" in un percorso di errore. In Rust sicuro, questi sono errori del compilatore e pulizia automatica simile a RAII con Drop. Gli errori del compilatore rappresentano un ciclo di feedback migliore di una guida di stile.
Storicamente, le riscritture sono un'idea terribile. Escludendo i commenti, Bun equivale a 535.496 righe di Zig. Una riscrittura in un'altra lingua richiederebbe un anno intero a un piccolo team di ingegneri. Significherebbe congelare le correzioni di bug, le correzioni di sicurezza o lo sviluppo di funzionalità per quel momento. L'approccio meno rischioso per ottenere qualcosa che possa essere spedito sarebbe un port meccanico da Zig a Rust, con il numero minimo di modifiche comportamentali, utilizzando esattamente la stessa suite di test che già utilizziamo per testare Bun.
Fortunatamente, la suite di test di Bun è scritta in TypeScript, il che significa che non dipende dal linguaggio di programmazione del runtime.
Un anno a impatto zero sugli utenti non è un'opzione realistica che potremmo prendere in considerazione. Pertanto, l'applicazione tramite lo stile del codice per risolvere i problemi di stabilità è stata la nostra soluzione migliore, ed era anche il nostro piano quando abbiamo aggiunto puntatori intelligenti ispirati a Rust alla codebase di Bun.
Ma onestamente, non volevo farlo. I puntatori intelligenti fatti in casa offrono un'ergonomia peggiore rispetto a Rust, senza alcuna garanzia.
E se, invece, passassi una settimana a testare se il nuovo modello di Anthropic può riscrivere Bun in Rust?
All'inizio non mi aspettavo che funzionasse. Dopo qualche giorno, un'alta percentuale della suite di test ha iniziato a superare e ho visto quanto il nuovo codice Rust corrispondeva al codice base Zig originale. La mia opinione è passata da "vale la pena provarlo" a "lo unirò".
Claude, riscrivi Bun in Rust.
Ci sono molti modi per fare un pessimo lavoro in questo senso. Ad esempio, chiedendo a Claude "Riscrivi Bun in Rust. Non commettere errori". e poi pregare che funzionasse non è quello che ho fatto.
Pensa a come una persona farebbe una cosa del genere. La prima grande domanda è:
Riscrittura incrementale? Oppure tutto tutto in una volta?
Nella mia esperienza nel porting del transpiler di esbuild da Go a Zig per la versione iniziale di Bun (senza LLM), tutto in una volta è migliore. Una riscrittura incrementale aggiunge codice temporaneo che speri venga eliminato prima o poi e sarebbe doloroso nel breve-medio termine.
La seconda grande domanda: come?
Come possiamo mantenere Bun in Rust lo stesso Bun di prima, con la stessa architettura, prestazioni e set di funzionalità ottenendo allo stesso tempo le funzionalità linguistiche di Rust come il controllo dei prestiti? Come possiamo garantire che il team possa ancora mantenerlo dopo la riscrittura?
Esegui la riscrittura in modo che sembri che abbiamo transpilato il nostro codice Zig in Rust. Possiamo gradualmente rifattorizzarlo per ridurre l'utilizzo di unsafe e assomigliare più a un Rust idiomatico dopo la distribuzione di Bun v1.4.
Queste sono le uniche due grandi domande. Tutto il resto è tattica.
Loop che scrivono e rivedono il codice
Gran parte del lavoro ingegneristico quotidiano degli ingegneri del software può essere eccessivamente semplificato in loop.
// Pseudocode, not real code:
let task;
while ((task = todoList.pop())) {
const result = task();
const feedback = await Promise.all([review(result), review(result)]);
await apply(feedback, result);
}
A task è associato un contesto (un ticket Jira, un problema di GitHub, ecc.). result è il codice che hai scritto per risolverlo. Revisore/i del codice review le modifiche per verificare regressioni e correttezza. E poi rispondi al feedback.
Ho riscritto Bun in Rust utilizzando circa 50 flussi di lavoro dinamici in Claude Code eseguiti ininterrottamente nel corso di 11 giorni.
Ogni flusso di lavoro dinamico era un ciclo come questo: un flusso di lavoro per:
- Genera una guida al porting che associa Zig modelli e tipi a Rust modelli e tipi
- Trasferisci meccanicamente ogni file
.zigin un file.rs, corrispondente a PORTING.md e LIFETIMES.tsv - Correggi gli errori del compilatore di ogni crate
- Fai in modo che i sottocomandi come
bun testobun buildfunzionino - Supera tutti i test dell'intera suite di test di Bun
- Diversi refactoring e passaggi di pulizia di grandi dimensioni
Per la maggior parte di quegli 11 giorni (e successivi), ho monitorato i flussi di lavoro, leggendo manualmente gli output per verificare la presenza di problemi e bug e chiedendo a Claude di modificare il ciclo per risolvere le cose.
Come si revisiona un PR con +1 milione di righe aggiunte? Come iniziare a creare la sicurezza necessaria per unire in modo responsabile grandi quantità di codice creato da LLM?
Una suite di test indipendente dal linguaggio con un milione di asserzioni, revisione del codice in contraddittorio e, quando qualcosa va storto, correzione del processo che genera il codice invece di correggere manualmente il codice.
Revisione contraddittoria
La revisione contraddittoria chiede a Claude (in una finestra di contesto separata) di fornire in modo esaustivo i motivi per cui le modifiche creano bug o non funzionano.
Finestre di contesto divise
Di solito con gli esseri umani, la persona che revisiona il codice non è la persona che ha creato il codice. La persona che scrive il codice desidera unire il codice, il che può influenzare le sue azioni per spedirlo prima che sia pronto.
Claude è allo stesso modo. Il Claude che ha scritto il codice vuole che il codice venga accettato. Il Claude che recensisce vuole trovare problemi nel codice.
1 implementatore, 2 o più revisori contraddittori per implementatore. L'unico compito del revisore: trovare bug e motivi per cui il codice non funziona. L'implementatore non effettua revisioni. Il revisore non implementa.
bug 1 di 3 · chiusura asincrona
il suo contesto: il .zig originale, il piano del porto, il suo ragionamento
il suo contesto: solo il diff. gli viene detto di presumere che il codice sia sbagliato.
src/runtime/api/bun/js_bun_spawn_bindings.rs · compila in modo pulito
per stdio in [spawned_stdout, spawned_stderr] {
corrisponde a stdio {
StdioResult::Buffer(mut pipe) => {
// pipe: Boxuv::Pipe — consegnalo a libuv per chiuderlo
pipe.close(Sottoprocesso::on_pipe_close)
}
StdioResult::Fd(fd) => fd.close(),
StdioResult::Non disponibile => {}
}
}
uv_close è asincrono: libuv mantiene il puntatore dell'handle grezzo fino al successivo tick del ciclo, quindi chiama on_pipe_close, che libera l'allocazione. Ma `pipe` è un Box che viene rilasciato alla fine di questo braccio di corrispondenza: libuv viene lasciato con la memoria liberata e la richiamata di chiusura la libera una seconda volta. Usalo dopo gratis, poi doppiamente gratis.
Box::leak(pipe).close(Sottoprocesso::su_pipe_close)
f0a454376c7 · win-review: js_bun_spawn_bindings.rs leak Boxuv::Pipe prima di async uv_close per evitare UAF/double-free in on_pipe_close
Tre bug effettivamente rilevati dai revisori contraddittori: ogni commit citato riporta l'attribuzione della revisione nella riga dell'oggetto. Tutti e tre compilati; tutti e tre sembravano plausibili. Il revisore è un secondo Claude nella propria finestra di contesto: ottiene la differenza e nient'altro - nessuno dei ragionamenti dell'implementatore - e gli viene detto di trovare la strada che è sbagliata. Il codice è condensato dai commit citati; stessi bug, stesse correzioni.
Che aspetto ha?
Se stai per fare qualcosa di grande e costoso, puoi risparmiare tempo e denaro riducendo prima i rischi.
Lavoro di preparazione
Prima di scrivere qualsiasi codice, ho trascorso circa 3 ore a parlare con Claude su come mappare i modelli dalla nostra codebase Zig strettamente a Rust. Claude ha serializzato questa discussione in un documento PORTING.md, che è finito su Hacker News.
La prossima domanda: come si aggiungono Rust durate al codice che gestisce manualmente la memoria?
È qui che ho suggerito a Claude qualcosa del genere:
Io: Diamo il via a un flusso di lavoro dinamico per analizzare la durata corretta di ogni campo struct nel codebase. Questo flusso di lavoro dovrebbe leggere ogni campo della struttura all'interno di ogni singolo file e tracciare il flusso di controllo. Innanzitutto, cerca i campi struct con durate complesse fino a express in Rust, quindi proponi una durata per quel campo, quindi utilizza 2 agenti di revisione contraddittori per rivedere tale durata, quindi applica qualsiasi feedback e serializza in un LIFETIMES.tsv affinché altri claudes possano esaminarli.
Quindi un giro di revisioni contraddittorie su PORTING.md e LIFETIMES.tsv insieme per risolvere eventuali suggerimenti contrastanti e ricontrollare tutto. Lo rileggo anche manualmente.
Esecuzione di prova
Prima di chiedere a Claude di tradurre tutti i 1.448 file .zig in file .rs, ho iniziato con solo 3. Per ciascuno dei 3 file, 1 implementatore ha scritto il nuovo file .rs, 2 revisori contraddittori hanno verificato che il file .rs corrispondeva al comportamento del file .zig e che seguiva PORTING.md e LIFETIMES.tsv. Successivamente, 1 riparatore ha applicato eventuali suggerimenti.
False partenze
Ho chiesto a Claude di eseguire il loop del flusso di lavoro su tutti i 1.448 file .zig e dopo circa 2 minuti un Claude ha eseguito git stash prima di impegnarsi. Un altro ha eseguito git stash pop. E poi git reset HEAD --hard. Si stavano calpestando a vicenda! E se inserisco ciascun Claude in un albero di lavoro separato, esaurirei lo spazio su disco perché il repository git di Bun è troppo grande e alla fine le modifiche dovranno essere compilate e visualizzate insieme.
Quindi, ho chiesto a Claude di modificare il flusso di lavoro per istruirlo a non eseguire mai git stash o git reset o qualsiasi comando git che non esegua il commit di un file specifico in una sola volta. Nemmeno cargo. Nessun comando lento.
Quindi, Claude ha ripreso i flussi di lavoro. E funzionava! Troppo lentamente, quindi l'ho diviso in soli 4 frammenti del flusso di lavoro ciascuno con il proprio albero di lavoro (4 alberi di lavoro in totale), ciascuno con 16 claude di commit e push di file.
Scrivo finalmente il codice
Grazie a tutta la parallelizzazione e a questo lavoro di preparazione, al picco Claude ha scritto circa 1.300 righe di codice al minuto. Ogni riga di codice è stata rivista da due revisori contraddittori separati (anche Claude) e ha subito una serie di correzioni prima di impegnarsi. Assolutamente niente di tutto ciò ha ancora funzionato.
11 giorni × 24 ore · PDT
5.463 commit
1695 commit/ora
00:00 6:00 12:00 18:00 4 maggio 4 maggio, 7:00 – 8:00 PDT — 6 commit, +89.278 righe 4 maggio, 8:00 – 9:00 PDT — 2 commit, +50.742 righe 4 maggio, 9:00 – 10:00 PDT — 1 commit, +28.149 righe 4 maggio, 11:00–12:00 PDT — 1 commit, +39.752 righe4 maggio, 12:00–13:00 PDT — 3 commit, +251.616 righe4 maggio, 13:00–14:00 PDT — 2 commit, +161.724 righe4 maggio, 15:00–16:00 PDT — 3 commit, +136.381 righe4 maggio, 17:00–18:00 PDT — 5 commit, +895 linee4 maggio, 18:00-19:00 PDT — 5 commit, +17.027 righe4 maggio, 19:00–20:00 PDT — 1 commit, +106 righe4 maggio, 21:00–22:00 PDT — 13 commit, +11.661 righe4 maggio, 23:00–24:00 PDT — 6 commit, +8.516 righe5 maggio5 maggio 00:00-1:00 PDT - 9 commit, +1.381 righe 5 maggio, 1:00-2:00 PDT - 7 commit, +1.577 righe 5 maggio, 2:00-3:00 PDT - 4 commit, +2.035 righe 5 maggio, 3:00-4:00 PDT - 4 commit, +7.808 righe 5 maggio, 4:00-5:00 PDT - 1 commit, +2.796 righe5 maggio, 5:00-6:00 PDT — 2 commit, +29.370 righe5 maggio, 8:00–9:00 PDT — 2 commit, +7.076 righe5 maggio, 9:00–10:00 PDT — 2 commit, +308 righe5 maggio, 11:00–12:00 PDT — 2 commit, +1.643 righe5 maggio 12:00-13:0 PDT — 4 commit, +1.452 righe 5 maggio, 13:00-14:00 PDT — 1 commit, +2.142 righe 5 maggio, 14:00–15:00 PDT — 4 commit, +7.787 righe 5 maggio, 15:00–16:00 PDT — 2 commit, +5.835 righe 5 maggio, 16:00–17:00 PDT — 1 commit, +3.417 righe5 maggio, 17:00-18:00 PDT — 4 commit, +3.960 righe5 maggio, 18:00–19:00 PDT — 4 commit, +9.179 righe5 maggio, 19:00–20:00 PDT — 4 commit, +1.983 righe5 maggio, 20:00–21:00 PDT — 4 commit, +18.902 righe5 maggio 21:00-22:00 PDT — 43 commit, +40.650 righe 5 maggio, 22:00-23:00 PDT — 139 commit, +64.842 righe 5 maggio, 23:00-24:00 PDT — 141 commit, +34.814 righe 6 maggio6 maggio, 00:00-1:00 PDT — 60 commit, +10.417 linee6 maggio, 1:00-2:00 PDT — 296 commit, +38.530 righe6 maggio, 2:00–3:00 PDT — 306 commit, +18.836 righe6 maggio, 3:00–4:00 PDT — 196 commit, +10.245 righe6 maggio, 4:00–5:00 PDT — 86 commit, +2.655 righemaggio 6, 5:00-6:00 PDT — 16 commit, +289 righe6 maggio, 8:00–9:00 PDT — 5 commit, +264 righe6 maggio, 9:00–10:00 PDT — 458 commit, +16.409 righe6 maggio, 10:00–11:00 PDT — 695 commit, +44.000 righe6 maggio 11:00-12:00 PDT — 102 commit, +21.972 righe 6 maggio, 12:00-13:00 PDT — 19 commit, +2.891 righe 6 maggio, 13:00-14:00 PDT — 3 commit, +56 righe 6 maggio, 15:00-16:00 PDT — 64 commit, +3.606 righe 6 maggio, 16:00-17:00 PDT — 264 commit, +60.132 righe 6 maggio, 17:00-18:00 PDT — 268 commit, +40.953 righe 6 maggio, 18:00-19:00 PDT — 281 commit, +16.283 righe 6 maggio, 19:00-20:00 PDT — 258 commit, +26.654 righe 6 maggio, 20:00-21:00 PDT — 327 commit, +16.599 righe 6 maggio, 21:00-22:00 PDT — 74 commit, +8.331 righe 6 maggio, 22:00-23:00 PDT — 17 commit, +2.200 righe PDT 6 maggio, 23:00-24:00 PDT — 11 commit, +3.590 righe 7 maggio7 maggio, 00:00-01:00 PDT PDT — 17 commit, +6.577 righe 7 maggio, 1:00-2:00 PDT — 22 commit, +8.718 righe 7 maggio, 2:00–3:00 PDT — 21 commit, +11.392 righe 7 maggio, 3:00–4:00 PDT — 53 commit, +6.476 righe 7 maggio, 4:00–5:00 PDT — 31 commit, +2.356 righe7 maggio, 5:00-6:00 PDT — 9 commit, +1.787 righe7 maggio, 6:00–7:00 PDT — 4 commit, +580 righe7 maggio, 7:00–8:00 PDT — 5 commit, +181 righe7 maggio, 11:00–12:00 PDT — 3 commit, +421 righe7 maggio 12:00-13:00 PDT — 1 commit, +13 righe7 maggio, 13:00–14:00 PDT — 5 commit, +248 righe7 maggio, 14:00–15:00 PDT — 9 commit, +2.131 righe7 maggio, 15:00–16:00 PDT — 51 commit, +3.207 righe7 maggio, 16:00–17:00 PDT — 56 commit, +2.647 righe7 maggio, 17:00-18:00 PDT — 159 commit, +2.787 righe7 maggio, 18:00–19:00 PDT — 42 commit, +1.590 righe7 maggio, 19:00–20:00 PDT — 46 commit, +4.170 righe7 maggio, 20:00–21:00 PDT — 52 commit, +2.113 righeMaggio 7, 21:00-22:00 PDT — 27 commit, +1.585 righe7 maggio, 22:00–23:00 PDT — 27 commit, +2.231 righe PDT7 maggio, 23:00–24:00 PDT — 30 commit, +4.987 righe8 maggio8 maggio, 00:00–1:00 PDT — 27 commit, +1.196 righemaggio 8, 1:00-2:00 PDT — 14 commit, +904 righe8 maggio, 2:00–3:00 PDT — 8 commit, +536 righe8 maggio, 3:00–4:00 PDT — 13 commit, +253 righe8 maggio, 4:00–5:00 PDT — 3 commit, +771 righe8 maggio, 5:00–6:00 PDT — 15 commit, +1.545 righe8 maggio, 6:00-7:00 PDT — 12 commit, +1.965 righe8 maggio, 7:00–8:00 PDT — 14 commit, +1.866 righe8 maggio, 8:00–9:00 PDT — 55 commit, +3.622 righe8 maggio, 9:00–10:00 PDT — 35 commit, +4.778 righeMaggio 8, 10:00-11:00 PDT — 1 commit, +0 righe8 maggio, 12:00–13:00 PDT — 1 commit, +116 righe8 maggio, 13:00–14:00 PDT — 2 commit, +66 righe8 maggio, 14:00–15:00 PDT — 9 commit, +1.071 righe8 maggio, 15:00–16:00 PDT — 26 commit, +1.691 righe8 maggio, 16:00-17:00 PDT — 18 commit, +2.751 righe8 maggio, 17:00–18:00 PDT — 2 commit, +97 righe8 maggio, 18:00–19:00 PDT — 2 commit, +135 righe8 maggio, 19:00–20:00 PDT — 11 commit, +1.763 righe8 maggio 20:00-21:00 PDT — 20 commit, +5.272 righe 8 maggio, 21:00-22:00 PDT — 12 commit, +952 righe PDT 8 maggio, 22:00-23:00 PDT — 2 commit, +334 righe 8 maggio, 23:00-24:00 PDT — 6 commit, +2.033 righe 9 maggio9 maggio, 00:00-01:00 PDT PDT — 9 commit, +387 righe 9 maggio, 1:00-2:00 PDT — 9 commit, +723 righe 9 maggio, 2:00–3:00 PDT — 8 commit, +98 righe 9 maggio, 3:00–4:00 PDT — 63 commit, +2.538 righe 9 maggio, 4:00–5:00 PDT — 11 commit, +8.861 righeMaggio 9, 5:00-6:00 PDT — 4 commit, +42 righe9 maggio, 6:00–7:00 PDT — 3 commit, +2.616 righe9 maggio, 7:00–8:00 PDT — 6 commit, +6.993 righe9 maggio, 8:00–9:00 PDT — 1 commit, +3.705 righe9 maggio, 9:00–10:00 PDT — 11 commit, +199 righe9 maggio, 11:00-12:00 PDT — 1 commit, +23 righe9 maggio, 12:00-13:00 PDT — 4 commit, +5.012 righe9 maggio, 13:00-14:00 PDT — 7 commit, +2.080 righe9 maggio, 14:00-15:00 PDT — 6 commit, +924 righe9 maggio 15:00-16:00 PDT — 5 commit, +248 righe9 maggio, 16:00–17:00 PDT — 17 commit, +508 righe9 maggio, 17:00–18:00 PDT — 2 commit, +135 righe9 maggio, 18:00–19:00 PDT — 4 commit, +822 righe9 maggio, 19:00–20:00 PDT — 1 commit, +7 righemaggio 1010 maggio, 00:00-01:00 PDT — 4 commit, +497 righe10 maggio, 01:00-02:00 PDT — 2 commit, +35 righe 10 maggio, 2:00-3:00 PDT — 1 commit, +131 righe10 maggio, 03:00-04:00 PDT — 2 commit, +322 righe10 maggio, 04:00-05:00 PDT — 1 commit, +3 righe10 maggio, 5:00-6:00 PDT — 1 commit, +26 righe10 maggio, 6:00-7:00 PDT — 2 commit, +81 righe10 maggio, 7:00-8:00 PDT — 1 commit, +5 righe10 maggio, 8:00-9:00 PDT — 4 commit, +78 righe10 maggio, 9:00-10:00 PDT — 1 commit, +1 righe10 maggio, 10:00-11:00 PDT — 2 commit, +128 righe10 maggio, 11:00-12:00 PDT — 1 commit, +4 righe10 maggio, 12:00-13:00 PDT — 2 commit, +413 righe10 maggio, 13:00-14:00 PDT — 1 commit, +25 righe10 maggio, 14:00-15:00 PDT PDT — 5 commit, +327 righe 10 maggio, 15:00-16:00 PDT — 6 commit, +1.172 righe 10 maggio, 16:00-17:00 PDT — 4 commit, +752 righe PDT 10 maggio, 17:00-18:00 PDT — 3 commit, +227 righe 10 maggio, 18:00-19:00 PDT — 2 commit, +242 righeMaggio 10, 19:00-20:00 PDT — 1 commit, +306 righe10 maggio, 20:00–21:00 PDT — 1 commit, +54 righe10 maggio, 21:00–22:00 PDT — 2 commit, +75 righe10 maggio, 22:00–23:00 PDT — 1 commit, +134 righe10 maggio, 23:00–24:00 PDT — 5 commit, +103 righe11 maggio11 maggio, 00:00-1:00 PDT — 2 commit, +150 righe11 maggio, 1:00-2:00 PDT — 4 commit, +398 righe11 maggio, 2:00-3:00 PDT — 2 commit, +364 righe11 maggio, 3:00-4:00 PDT — 3 commit, +44 righe11 maggio 4:00-5:00 PDT — 7 commit, +9.367 righe11 maggio, 6:00–7:00 PDT — 2 commit, +43 righe11 maggio, 7:00–8:00 PDT — 2 commit, +149 righe11 maggio, 8:00–9:00 PDT — 10 commit, +2.171 righe11 maggio, 9:00–10:00 PDT — 16 commit, +2.047 righe11 maggio, 10:00–11:00 PDT — 18 commit, +3.356 righe11 maggio, 11:00–12:00 PDT — 9 commit, +861 righe11 maggio, 12:00–13:00 PDT — 3 commit, +412 righe11 maggio, 13:00–14:00 PDT — 12 commit, +2.978 righe11 maggio, 14:00-15:00 PDT — 157 commit, +10.700 righe11 maggio, 15:00–16:00 PDT — 16 commit, +1.346 righe11 maggio, 16:00–17:00 PDT — 3 commit, +78 righe11 maggio, 17:00–18:00 PDT — 41 commit, +2.568 linee11 maggio, 18:00-19:00 PDT — 55 commit, +4.912 righe11 maggio, 19:00–20:00 PDT — 53 commit, +3.475 righe11 maggio, 20:00–21:00 PDT — 32 commit, +1.732 righe11 maggio, 21:00–22:00 PDT — 46 commit, +4.506 righemaggio 11, 22:00-23:00 PDT — 45 commit, +1.711 righe 11 maggio, 23:00–24:00 PDT — 52 commit, +10.850 righe 12 maggio12 maggio, 00:00–1:00 PDT — 30 commit, +3.760 righe 12 maggio, 1:00–2:00 PDT — 24 commit, +9.443 righe12 maggio, 2:00-3:00 PDT — 41 commit, +1.635 righe12 maggio, 3:00–4:00 PDT — 39 commit, +788 righe12 maggio, 4:00–5:00 PDT — 27 commit, +651 righe12 maggio, 5:00–6:00 PDT — 23 commit, +779 righeMaggio 12, 6:00-7:00 PDT — 1 commit, +137.576 righe 12 maggio, 7:00–8:00 PDT — 2 commit, +81 righe 12 maggio, 8:00–9:00 PDT — 2 commit, +75 righe 12 maggio, 9:00–10:00 PDT — 2 commit, +130 righe 12 maggio, 10:00–11:00 PDT — 5 commit, +160 righe 12 maggio, 11:00-12:00 PDT — 2 commit, +20 righe 12 maggio, 12:00-13:00 PDT — 1 commit, +2 righe 12 maggio, 13:00-14:00 PDT — 30 commit, +2.677 righe 12 maggio, 14:00-15:00 PDT — 41 commit, +7.022 righemaggio 12, 15:00-16:00 PDT — 4 commit, +200 righe 12 maggio, 16:00–17:00 PDT — 27 commit, +1.423 righe 12 maggio, 17:00–18:00 PDT — 19 commit, +1.055 righe 12 maggio, 18:00–19:00 PDT — 2 commit, +380 righe PDT 12 maggio, 19:00–20:00 PDT — 2 commit, +84 righe 12 maggio, 21:00-22:00 PDT — 7 commit, +273 righe 12 maggio, 22:00-23:00 PDT — 3 commit, +230 righe 12 maggio, 23:00-24:00 PDT — 7 commit, +319 righe 13 maggio13 maggio, 00:00-1:00 PDT — 2 commit, +133 righe13 maggio, 1:00-2:00 PDT — 14 commit, +2.177 righe13 maggio, 2:00–3:00 PDT — 12 commit, +685 righe13 maggio, 4:00–5:00 PDT — 10 commit, +657 righe13 maggio, 5:00–6:00 PDT — 1 commit, +687 righe13 maggio 6:00-7:00 PDT — 11 commit, +380 righe13 maggio, 7:00–8:00 PDT — 12 commit, +5.247 righe13 maggio, 8:00–9:00 PDT — 14 commit, +1.051 righe13 maggio, 9:00–10:00 PDT — 7 commit, +680 righe13 maggio, 10:00–11:00 PDT — 10 commit, +412 righe 13 maggio, 11:00-12:00 PDT — 6 commit, +314 righe 13 maggio, 12:00-13:00 PDT — 10 commit, +2.980 righe PDT — 1 commit, 13:00-14:00 PDT — 1 commit, +0 righe 13 maggio, 14:00-15:00 PDT — 3 commit, +439 righemaggio 13, 17:00-18:00 PDT — 7 commit, +114 righe13 maggio, 18:00–19:00 PDT — 4 commit, +605 righe13 maggio, 21:00–22:00 PDT — 1 commit, +13 righe13 maggio, 22:00–23:00 PDT — 1 commit, +48 righe13 maggio, 23:00–00:00 PDT — 1 commit, +8 righe14 maggio14 maggio, 00:00-1:00 PDT — 1 commit, +150 righe
Ogni commit sul ramo della porta (fusioni escluse), suddiviso per ora. Ora di punta: 695 commit.
Noti i tempi incoerenti? Ho dimenticato di aumentare gli IOPS predefiniti sull'istanza EC2 su cui è stato eseguito. È bastato un comando grep lento per bloccare le letture e le scritture del disco per minuti.
Errori del compilatore come coda di lavoro
Dopo aver scritto tutto il codice, ho chiesto a Claude di scrivere un flusso di lavoro correggendo ogni errore del compilatore. Abbiamo lavorato cassa per cassa.
≈15.125 errori rimasti
Mercoledì 6 maggio, 1:29 PDT
errors.txt88 corregge i commit
errore: src/event_loop/SpawnSyncEventLoop.rs
errore: src/sys/lib.rs
errore: runtime/timer/TimerObjectInternals.rs
errore: src/runtime/ffi/ffi_body.rs
errore: src/http/lib.rs
errore: src/js_parser/ast/Parser.rs
errore: JSPromise.rs
errore: autonomo_graph/StandaloneModuleGraph.rs
errore: src/http_jsc/websocket_client.rs
errore: doStep5.rs
errore: src/install/PackageManager.rs
diviso · 64 claude
albero di lavoro 1
→→
→→
→→
→→
albero di lavoro 2
→→
→→
→→
→→
albero di lavoro 3
→→
→→
→→
→→
albero di lavoro 4
→→
→→
→→
→→
1 correzione2 revisione1 applicabile
→ impegna terra per cassa
panino_bundler0
bun_js_parser17
panino_css10
panino_http3
bun_sys3
bun_alloc1
panino_sourcemap3
panino_ini0
panino_analytics1
bun_zlib0
· fase-d(sql_jsc/mysql/protocol): correzioni importazioni, limiti ReaderContext, helper WTFStringImpl
· fase-d(tier0): bun_sourcemap parse_json: campo BabyList.len, Option<StoreRef> unwrapping
· fase-d(jpa): skipTypescript.rs — porta corpi reali, rilascia stub _draft/todo
Come ha funzionato la fase D, riprodotto dai suoi 1.610 commit reali (6 maggio, PDT): il controllo del carico ha scritto ≈16.000 errori in un file, raggruppati per cassa; il flusso di lavoro li ha suddivisi tra 64 Claude: 16 cicli su 4 alberi di lavoro, ciascuno dei quali corregge Claude, due rivede, uno applica. Ogni chip è un insieme di impegni reali: atterra sulla sua cassa reale e solo allora i contatori si muovono. Le righe di errore sono oggetti di commit reali.
La classe di errore più complicata riguardava le dipendenze cicliche.
La nostra base di codice Zig era un'unità di compilazione (effettivamente una cassa). Volevo dividere la nuova codebase Rust in ~100 casse in modo che Rust venisse compilato più velocemente, ma ciò doveva evitare dipendenze cicliche riducendo al minimo le modifiche rispetto all'implementazione Zig originale. Il mio PR per eseguire questa operazione immediatamente prima di iniziare la riscrittura Rust non era sufficiente. Invece di ricominciare da capo, ho eseguito un altro flusso di lavoro per classificare dove dovrebbe andare il codice con dipendenze cicliche e annotare tutto, quindi un altro flusso di lavoro per eseguire il refactoring.
La correzione delle dipendenze cicliche ha rivelato circa 16.000 errori del compilatore. Un numero enorme per 1 essere umano, ma non un numero folle per 64 claude contemporaneamente.
Per massimizzare il parallelismo, il flusso di lavoro si è ripetuto su ciascuna cassa.
- Per ogni cassa, esegui
cargo check, raggruppa l'output per file e salva gli errori in un file - Correggi tutti gli errori del compilatore all'interno di quel crate
- 2 revisori contraddittori per le modifiche alla cassa
- 1 riparatore applica le correzioni
Per evitare che Claudes si pesti a vicenda, cargo check ha corso solo all'inizio e, come le altre corse, niente git fino alla fine.
Un'altra falsa partenza
Claude ha interpretato "facciamo compilare tutti i crate" come "elimina le funzioni con errori di compilazione". Claude ha anche iniziato ad aggiungere commenti esplicativi sospettosamente lunghi per documentare soluzioni alternative, quindi ho aggiunto questa regola affinché i revisori contraddittori possano rifiutarla:
Se hai bisogno di un commento lungo un paragrafo per giustificare il motivo per cui la soluzione alternativa è corretta, il codice è sbagliato: correggi il codice.
Una modifica immediata e poche ore dopo, queste cose hanno smesso di accadere.
Test del fumo
Le modelle adorano dire "test del fumo"
Una volta superato cargo check, il passo successivo è stato farlo compilare ed eseguire bun --version. Aveva errori del linker. Quindi, è andato nel panico immediatamente all'avvio.
L'obiettivo successivo era farlo funzionare bun test <file>. Una volta che ha funzionato, abbiamo potuto iniziare a eseguire i test! È ora di un altro flusso di lavoro, eseguendo il looping sui sottocomandi della CLI di bun:
- Salva ogni stacktrace in errore in un file insieme al relativo sottocomando
- Per ogni stacktrace non riuscito raggruppato per sottocomando, avere 1 correzione Claude
- 2 revisori contraddittori
- 1 riparatore applica i suggerimenti
Fai passare la suite di test localmente
Questo flusso di lavoro si è ripetuto sui file di test.
Esegui circa 100 file di test casuali suddivisi in uno dei 4 alberi di lavoro per cartella nella codebase. Per ogni test fallito, salva lo stacktrace e gli errori in un file, 1 implementatore propone una correzione, 2 revisori contraddittori, quindi si applica 1 correttore.
Ancora altre false partenze
La nostra suite di test include molti test di perdita di memoria e una manciata di test di integrazione che possono richiedere più di un minuto, ad esempio: un test che esegue next dev e controlla il ricaricamento del modulo caldo può rilevare le modifiche 100 volte. Molti di questi test scadono nelle build di debug.
Abbiamo anche test di stress che esauriscono il numero massimo di socket TCP sulla macchina, test che leggono e scrivono gigabyte su disco e test che generano ~10.000 processi.
Ciò richiedeva un isolamento più forte di "per favore", quindi abbiamo utilizzato systemd-run (cgroups) per limitare l'utilizzo di memoria e CPU e isolare gli spazi dei nomi pid. La macchina ha esaurito lo spazio su disco e si è comunque bloccata più volte.
Ottieni il passaggio della suite di test in CI
Due giorni dopo la prima esecuzione del CI, l'elenco degli errori è sceso da 972 file di test a 23. Un giorno e mezzo dopo, Linux è diventato completamente verde e, per la prima volta, sembrava che questa riscrittura di Rust avrebbe davvero funzionato.
0/6 piattaforme verdi
build n. 53047 · sabato 9 maggio, 11:52 PDT
macOS x64 · 2 frammenti
build #52897: shard Failuresbuild #52932: shard Failuresbuild #52934: shard Failuresbuild #52938: shard Failuresbuild #52944: shard Failuresbuild #52946: shard Failuresbuild #52949: shard Failuresbuild #52975: shard Failuresbuild #52998: shard Failuresbuild #53007: shard Failuresbuild #53015: shard Failuresbuild #53026: shard Failuresbuild #53027: shard Failuresbuild #53035: shard Failuresbuild #53041: shard Failuresbuild #53047: shard Failuresbuild #53056: shard Failuresbuild #53077: shard Failuresbuild #53090: shard Failuresbuild #53095: shard Failuresbuild #53106: shard Failuresbuild #53109: shard Failuresbuild #53123: shard Failuresbuild #53127: shard Failuresbuild #53130: shard Failuresbuild #53131: shard Failuresbuild #53133: shard Failuresbuild #53134: shard Failuresbuild #53143: shard Failuresbuild #53149: tutti i frammenti passatibuild #53159: tutti i frammenti passatibuild #53164: tutti i frammenti passatibuild #53167: tutti i frammenti passatibuild #53172: tutti i frammenti passatibuild #53176: tutti i frammenti passatibuild #53194: build di tutti i frammenti passati #53208: build di tutti i frammenti passati #53213: build di errori di shard #53214: build di errori di shard #53216: build di nessun errore (esecuzione parziale) #53222: build di tutti i frammenti passati #53229: build di nessun errore (esecuzione parziale) #53241: build di tutti i frammenti passati #53265: build di tutti i frammenti passati #53271: build di tutti i frammenti passati #53304: build di errori di shard #53327: build di errori di shard #53340: build di errori di shard #53401: build di errori di shard #53431: build di errori di shard #53491: tutti i frammenti passatibuild #53503: tutti shards passatibuild #53748: shard Failuresbuild #53753: tutti gli shard passatibuild #53787: tutti gli shard passatibuild #53811: tutti gli shard passatibuild #53933: nessun errore (esecuzione parziale)build #53952: tutti gli shard passatibuild #53983: shard Failuresbuild #53992: shard Failuresbuild #53999: build di errori di shard #54012: build di tutti i frammenti passati #54015: build di errori di shard #54017: build di tutti i frammenti passati #54022: build di errori di shard #54026: build di errori di shard #54033: build di tutti i frammenti passati #54040: build di tutti i frammenti passati #54047: shard Failuresbuild #54049: shard Failuresbuild #54057: shard Failuresbuild #54064: tutti gli shard passatibuild #54074: tutti gli shard passatibuild #54093: tutti gli shard passatibuild #54144: nessun errore (esecuzione parziale)build #54161: shard Failuresbuild #54186: shard Failuresbuild #54189: build di errori di shard #54196: build di errori di shard #54202: tutti gli shard passati
✓
Linux arm64 · 60 frammenti
build #52934: shard Failuresbuild #52938: shard Failuresbuild #52944: shard Failuresbuild #52969: shard Failuresbuild #52975: shard Failuresbuild #52980: shard Failuresbuild #52988: shard Failuresbuild #52996: shard Failuresbuild #52998: shard Failuresbuild #53007: shard Failuresbuild #53013: shard Failuresbuild #53014: shard Failuresbuild #53015: shard Failuresbuild #53026: shard Failuresbuild #53027: shard Failuresbuild #53031: nessun errore (esecuzione parziale)build #53032: shard Failuresbuild #53035: shard Failuresbuild #53041: shard Failuresbuild #53047: shard Failuresbuild #53056: shard Failuresbuild #53059: shard Failuresbuild #53077: shard Failuresbuild #53083: shard Failuresbuild #53086: shard Failuresbuild #53090: shard Failuresbuild #53095: shard Failuresbuild #53106: shard Failuresbuild #53109: shard Failuresbuild #53123: shard Failuresbuild #53127: shard Failuresbuild #53130: shard Failuresbuild #53131: shard Failuresbuild #53133: shard Failuresbuild #53134: shard Failuresbuild #53135: build di errori di shard #53143: build di errori di shard #53149: build di errori di shard #53159: build di errori di shard #53164: build di errori di shard #53167: tutti i frammenti passatibuild #53172: build di errori di shard #53176: tutti i frammenti passatibuild #53188: shard Failuresbuild #53194: shard Failuresbuild #53208: tutti i frammenti passatibuild #53212: shard Failuresbuild #53213: shard Failuresbuild #53214: shard Failuresbuild #53216: tutti i frammenti passatibuild #53222: tutti i frammenti passatibuild #53229: tutti i frammenti passatibuild #53236: nessun errore (esecuzione parziale)build #53241: tutti i frammenti passatibuild #53260: tutti i frammenti passatibuild #53265: tutti i frammenti passatibuild #53271: tutti i frammenti passatibuild #53280: nessun errore (esecuzione parziale)build #53298: nessun errore (esecuzione parziale)build #53304: errori dello shardbuild #53327: tutto shards passatabuild #53340: tutti gli shard passatibuild #53360: nessun errore (esecuzione parziale)build #53419: shard Failuresbuild #53431: shard Failuresbuild #53458: nessun errore (esecuzione parziale)build #53485: shard Failuresbuild #53491: shard Failuresbuild #53503: shard Failuresbuild #53514: build senza errori (esecuzione parziale) #53570: build con errori shard #53583: build con errori shard #53599: build con errori shard #53748: build con errori shard #53753: tutti gli shard passatibuild #53762: build senza errori (esecuzione parziale) #53787: build senza errori (esecuzione parziale) #53811: build di tutti i frammenti passati #53852: nessun errore (esecuzione parziale) build #53863: build di errori di shard #53893: build di errori di shard #53914: build di tutti i frammenti passati #53933: build di tutti i frammenti passati #53952: build di tutti i frammenti passati #53983: build di errori di shard #53992: shard Failuresbuild #53999: shard Failuresbuild #54008: nessun errore (esecuzione parziale)build #54012: tutti gli shard passatibuild #54015: tutti gli shard passatibuild #54017: tutti gli shard passatibuild #54022: shard Failuresbuild #54026: tutti gli shard passatibuild #54030: shard Failuresbuild #54033: build di tutti i frammenti passati #54040: build di tutti i frammenti passati #54047: build di errori di shard #54049: build di errori di shard #54055: build di errori di shard #54057: build di errori di shard #54064: build di tutti i frammenti passati #54074: build di tutti i frammenti passati #54083: nessun errore (esecuzione parziale)build #54093: tutti i frammenti passatibuild #54144: tutti i frammenti passatibuild #54161: errori degli shardbuild #54186: errori degli shardbuild #54189: errori degli shardbuild #54196: errori degli shardbuild #54202: tutti i frammenti passati
✓
Linux x64 · 60 frammenti
build #52934: shard Failuresbuild #52938: shard Failuresbuild #52944: shard Failuresbuild #52969: shard Failuresbuild #52975: shard Failuresbuild #52988: shard Failuresbuild #52996: shard Failuresbuild #52998: shard Failuresbuild #53007: shard Failuresbuild #53013: shard Failuresbuild #53014: shard Failuresbuild #53015: shard Failuresbuild #53026: shard Failuresbuild #53027: shard Failuresbuild #53032: shard Failuresbuild #53033: shard Failuresbuild #53035: shard Failuresbuild #53041: shard Failuresbuild #53047: shard Failuresbuild #53056: shard Failuresbuild #53059: nessun errore (esecuzione parziale)build #53077: shard Failuresbuild #53083: nessun errore (esecuzione parziale)build #53086: shard Failuresbuild #53090: shard Failuresbuild #53095: shard Failuresbuild #53106: shard Failuresbuild #53109: shard Failuresbuild #53123: shard Failuresbuild #53127: shard Failuresbuild #53130: shard Failuresbuild #53131: shard Failuresbuild #53133: shard Failuresbuild #53134: shard Failuresbuild #53135: shard Failuresbuild #53143: build di errori di shard #53149: build di errori di shard #53159: build di errori di shard #53164: build di errori di shard #53167: build di tutti gli shard passati #53172: build di tutti gli shard passati #53176: build di tutti gli shard passati #53188: build di errori di shard #53194: tutti shardspassatibuild #53208: tutti i frammenti passatibuild #53212: shard Failuresbuild #53213: shard Failuresbuild #53214: shard Failuresbuild #53216: tutti i frammenti passatibuild #53222: tutti i frammenti passatibuild #53229: tutti i frammenti passatibuild #53236: nessun errore (esecuzione parziale)build #53241: tutti i frammenti passatibuild #53260: tutti i frammenti passatibuild #53265: tutti i frammenti passatibuild #53271: tutti i frammenti passatibuild #53280: nessun errore (esecuzione parziale) build #53304: tutti i frammenti passatibuild #53327: tutti i frammenti passatibuild #53340: tutti i frammenti passatibuild #53360: build senza errori (esecuzione parziale) #53419: build senza errori (esecuzione parziale) #53431: build con errori di shard #53458: build senza errori (esecuzione parziale) #53485: build con errori di shard #53491: build con tutti i frammenti passati #53503: build con tutti i frammenti passati #53514: build senza errori (esecuzione parziale) #53570: build errori shard #53583: build errori shard #53599: build errori shard #53748: build errori shard #53753: tutti gli shard passatibuild #53759: nessun errore (esecuzione parziale)build #53781: build errori shard #53787: nessun errore (esecuzione parziale)build #53811: all shardspassatobuild #53863: shard Failuresbuild #53893: shard Failuresbuild #53914: tutti gli shardspassatibuild #53933: shard Failuresbuild #53952: tutti gli shardspassatibuild #53983: shard Failuresbuild #53992: shard Failuresbuild #53999: shard Failuresbuild #54008: nessun errore (esecuzione parziale) build #54012: tutti i frammenti passati build # 54015: tutti i frammenti passati build # 54017: tutti i frammenti passati build # 54022: tutti i frammenti passati build # 54026: tutti i frammenti passati build # 54030: tutti i frammenti passati build # 54033: tutti i frammenti passati build #54040: build senza errori (esecuzione parziale) #54047: build con errori shard #54049: build con errori shard #54055: build con errori shard #54057: build con errori shard #54064: build con tutti i frammenti passati #54074: build con tutti i frammenti passati #54083: build senza errori (esecuzione parziale) #54093: build di tutti i frammenti passati #54144: build di tutti i frammenti passati #54161: build di errori dello shard #54186: build di errori dello shard #54189: build di errori dello shard #54196: build di errori dello shard #54202: tutti gli shard passati
✓
macOS arm64 · 4 frammenti
build #52897: shard Failuresbuild #52929: shard Failuresbuild #52932: shard Failuresbuild #52944: shard Failuresbuild #52975: shard Failuresbuild #52996: shard Failuresbuild #52998: shard Failuresbuild #53007: shard Failuresbuild #53013: shard Failuresbuild #53014: shard Failuresbuild #53015: shard Failuresbuild #53026: shard Failuresbuild #53027: shard Failuresbuild #53032: shard Failuresbuild #53035: shard Failuresbuild #53041: shard Failuresbuild #53047: shard Failuresbuild #53056: shard Failuresbuild #53059: shard Failuresbuild #53077: shard Failuresbuild #53095: shard Failuresbuild #53109: shard Failuresbuild #53123: shard Failuresbuild #53127: shard Failuresbuild #53130: shard Failuresbuild #53131: shard Failuresbuild #53133: shard Failuresbuild #53134: shard Failuresbuild #53135: shard Failuresbuild #53143: shard Failuresbuild #53149: shard Failuresbuild #53159: shard Failuresbuild #53164: shard Failuresbuild #53167: shard Failuresbuild #53172: shard Failuresbuild #53176: shard Failuresbuild #53188: shard Failuresbuild #53194: shard Failuresbuild #53208: shard Failuresbuild #53212: shard Failuresbuild #53213: shard Failuresbuild #53214: shard Failuresbuild #53216: shard Failuresbuild #53222: shard Failuresbuild #53229: no build di errori (esecuzione parziale) #53236: nessun errore (esecuzione parziale)build #53241: tutti i frammenti passatibuild #53265: tutti i frammenti passatibuild #53271: nessun errore (esecuzione parziale)build #53280: nessun errore (esecuzione parziale)build #53304: build di errori dello shard #53327: nessun errore (esecuzione parziale)build #53340: build senza errori (esecuzione parziale) #53360: build senza errori (esecuzione parziale) #53368: build errori shard #53379: build errori shard #53383: build errori shard #53401: build errori shard #53431: build errori shard #53458: build errori shard #53491: no build errori (esecuzione parziale) #53503: build errori shard #53570: build errori shard #53583: build errori shard #53599: build errori shard #53601: build errori shard #53748: build errori shard #53753: tutti gli shard passatibuild #53757: build nessun errore (esecuzione parziale) #53759: build senza errori (esecuzione parziale) #53787: build senza errori (esecuzione parziale) #53811: build con errori di tutti gli shard #53952: build senza errori (esecuzione parziale) #53992: build con errori di shard #53999: build con errori di shard #54007: build senza errori (esecuzione parziale) #54012: build con tutti i frammenti passati #54015: build di errori di shard #54017: build di errori di shard #54022: build di errori di shard #54026: nessun errore (esecuzione parziale) build #54030: build di errori di shard #54033: build di errori di shard #54040: build di errori di shard #54047: build di errori di shard #54049: shard Failuresbuild #54055: shard Failuresbuild #54057: shard Failuresbuild #54064: shard Failuresbuild #54074: shard Failuresbuild #54093: shard Failuresbuild #54161: shard Failuresbuild #54186: shard Failuresbuild #54189: shard Failuresbuild #54196: shard Failuresbuild #54202: tutti gli shard sono passati
✓
Windows x64 · 8 frammenti
build #53090: shard Failuresbuild #53094: shard Failuresbuild #53095: shard Failuresbuild #53106: shard Failuresbuild #53109: shard Failuresbuild #53123: shard Failuresbuild #53127: shard Failuresbuild #53130: shard Failuresbuild #53131: shard Failuresbuild #53133: shard Failuresbuild #53134: shard Failuresbuild #53135: shard Failuresbuild #53143: shard Failuresbuild #53149: shard Failuresbuild #53159: shard Failuresbuild #53164: shard Failuresbuild #53167: shard Failuresbuild #53172: shard Failuresbuild #53176: shard Failuresbuild #53188: shard Failuresbuild #53194: shard Failuresbuild #53208: shard Failuresbuild #53212: shard Failuresbuild #53213: shard Failuresbuild #53214: shard Failuresbuild #53216: shard Failuresbuild #53222: shard Failuresbuild #53229: shard Failuresbuild #53236: shard Failuresbuild #53241: shard Failuresbuild #53260: shard Failuresbuild #53265: shard Failuresbuild #53271: shard Failuresbuild #53280: shard Failuresbuild #53298: shard Failuresbuild #53304: build di errori shard #53327: tutti gli shard passatibuild #53340: tutti gli shard passatibuild #53360: nessun errore (esecuzione parziale)build #53419: errori di shardbuild #53431: errori di shardbuild #53458: errori di shardbuild #53470: nessun errore (esecuzione parziale)build #53485: errori di shardbuild #53491: build senza errori (esecuzione parziale) #53503: build con errori shard #53514: build senza errori (esecuzione parziale) #53565: build senza errori (esecuzione parziale) #53570: build con errori shard #53599: build con errori shard #53745: build con errori shard #53748: build con errori shard #53753: shard Failuresbuild #53757: shard Failuresbuild #53759: shard Failuresbuild #53762: shard Failuresbuild #53769: shard Failuresbuild #53781: shard Failuresbuild #53787: shard Failuresbuild #53808: shard Failuresbuild #53811: shard Failuresbuild #53852: build di errori di shard #53863: build di errori di shard #53883: build di errori di shard #53893: build di errori di shard #53914: tutti i frammenti passatibuild #53933: tutti i frammenti passatibuild #53952: tutti i frammenti passatibuild #53973: nessun errore (esecuzione parziale)build #53983: shard Failuresbuild #53992: shard Failuresbuild #53999: shard Failuresbuild #54002: nessun errore (esecuzione parziale)build #54004: nessun errore (esecuzione parziale)build #54007: shard Failuresbuild #54008: nessun errore (esecuzione parziale)build #54012: tutti gli shard passatibuild #54015: tutti shardspassatibuild #54017: tutti i frammenti passatibuild #54022: shard Failuresbuild #54026: tutti i frammenti passatibuild #54030: tutti i frammenti passatibuild #54033: tutti i frammenti passatibuild #54040: tutti i frammenti passatibuild #54047: shard Failuresbuild #54049: shard Failuresbuild #54055: build di errori di shard #54057: build di errori di shard #54064: build di tutti i frammenti passati #54074: build di tutti i frammenti passati #54083: build di tutti i frammenti passati #54093: build di tutti i frammenti passati #54144: build di errori di shard #54161: build di errori di shard #54186: shard Failuresbuild #54189: shard Failuresbuild #54196: shard Failuresbuild #54202: tutti gli shard passati
✓
Windows arm64 · 8 frammenti
build #53090: shard Failuresbuild #53095: shard Failuresbuild #53106: shard Failuresbuild #53109: shard Failuresbuild #53123: shard Failuresbuild #53127: shard Failuresbuild #53130: shard Failuresbuild #53131: shard Failuresbuild #53134: shard Failuresbuild #53135: shard Failuresbuild #53149: shard Failuresbuild #53159: shard Failuresbuild #53164: shard Failuresbuild #53167: shard Failuresbuild #53172: shard Failuresbuild #53176: shard Failuresbuild #53188: shard Failuresbuild #53194: shard Failuresbuild #53208: shard Failuresbuild #53212: shard Failuresbuild #53213: shard Failuresbuild #53214: shard Failuresbuild #53216: shard Failuresbuild #53222: shard Failuresbuild #53229: shard Failuresbuild #53236: nessun errore (esecuzione parziale)build #53241: build errori shard #53260: build errori shard #53265: build errori shard #53271: build errori shard #53304: build errori shard #53327: tutti gli shard passatibuild #53340: tutti gli shard passatibuild #53360: nessun errore (esecuzione parziale)build #53419: no Failures (esecuzione parziale) build #53431: Shard Failuresbuild #53458: nessun errore (esecuzione parziale)build #53485: Shard Failuresbuild #53491: nessun errore (esecuzione parziale)build #53503: Nessun errore (esecuzione parziale)build #53599: Shard Failuresbuild #53748: Shard Failuresbuild #53753: shard Failuresbuild #53757: shard Failuresbuild #53759: shard Failuresbuild #53762: shard Failuresbuild #53787: shard Failuresbuild #53808: shard Failuresbuild #53811: shard Failuresbuild #53852: shard Failuresbuild #53863: shard Failuresbuild #53883: shard Failuresbuild #53893: shard Failuresbuild #53914: nessun errore (esecuzione parziale)build #53933: tutti gli shard passatibuild #53952: tutti gli shard passatibuild #53983: shard Failuresbuild #53992: shard Failuresbuild #53999: shard Failuresbuild #54007: nessun errore (esecuzione parziale)build #54012: tutti i frammenti passati build # 54015: tutti i frammenti passati build # 54017: tutti i frammenti passati build # 54022: tutti i frammenti passati build # 54026: tutti i frammenti passati build # 54030: nessun errore (esecuzione parziale) build # 54033: tutti i frammenti passati build # 54040: tutti i frammenti passati build #54047: build di errori di shard #54049: build di errori di shard #54055: build di nessun errore (esecuzione parziale) #54057: build di errori di shard #54064: build di tutti i frammenti passati #54074: build di tutti i frammenti passati #54083: build di nessun errore (esecuzione parziale) #54093: build di tutti i frammenti passati #54144: build di errori di shard #54161: build di errori di shard #54186: build di errori di shard #54189: build di errori di shard #54196: build di errori di shard #54202: tutti gli shard passati
✓
I frammenti di test di ogni build CI, per piattaforma, attraverso 135 build che hanno eseguito test (420 estratti da BuildKite). Verde brillante: ogni scheggia passava. Verde scuro: nessun guasto, ma la corsa è stata interrotta (sostituita). Rosso: almeno uno shard è guasto. Ogni corsia viene timbrata al primo passaggio della suite completa: i 60 frammenti di Linux erano verdi quasi un giorno intero prima di Windows. Le piattaforme continuarono a vacillare fino a quando non caddero gli ultimi test falliti; la build finale tutta verde era la #54202.
Il resto del tempo che ha preceduto la fusione è stato semplice. Un flusso di lavoro che si ripeteva correggendo gli errori dei test CI per ciascuna piattaforma finché non si verificavano più errori dei test. Diversi flussi di lavoro per la pulizia relativa a Windows, per deduplicare il codice, per ridurre l'utilizzo non sicuro e in generale per ripulire parte del codice.
Unire la riscrittura Rust
Una volta che il 100% della suite di test di Bun è passato in CI su tutte le piattaforme (e ho verificato manualmente che i test fossero effettivamente in esecuzione e non venissero saltati), ho eseguito una serie di comandi localmente per testare le cose, quindi ho premuto il pulsante Unisci.
L'unione in main non è una versione con versione. A questo punto, ero abbastanza fiducioso da andare avanti e impegnarmi nella riscrittura, ma non ancora abbastanza fiducioso da pubblicarlo.
Statistiche
Al massimo, eseguivamo 4 di questi flussi di lavoro contemporaneamente, ciascuno in un albero di lavoro separato, ciascuno con 16 Claude per flusso di lavoro. Circa 64 Claude alla volta.
log git · claude/phase-a-portpeak: 58 commit in un minuto
52
si impegna
+796,601
righe scritte, riscritture incluse
Martedì 5 maggio, 00:39 PDT
primo batch di 100 bozze PR n. 30412 aperto unito
· fase-a: bozza batch head-1 (100 file)+12.379−0
· fase-b1: percorsi + compilazione di stringhe (bozze di gate; superficie minima di codifica/stringhe/lexer)+2,312−2,149
Tutti i 6.502 commit (unioni escluse), riprodotti. Le barre rosa sono per lo più codici nuovi; le barre ciano sono per lo più cancellate. Il contatore di riga conta ogni riscrittura lungo il percorso: la differenza ottenuta è stata +1.009.272. Il registro contiene messaggi di commit reali.
0 test saltati o eliminati
11 giorni (3 maggio → fusione il 14 maggio) · 6.778 commit
| Piattaforma | chiamate wait() | Test | File |
|---|---|---|---|
| Debian 13x64 | 1,386,826 | 60,624 | 4,174 |
| macOS 14 arm64 | 1,259,953 | 58,850 | 4,175 |
| Windows 2019 x64 | 1,007,544 | 57,337 | 4,173 |
Pre-fusione, sono stati necessari 5,9 miliardi di token di input non memorizzati nella cache, 690 milioni di token di output e 72 miliardi di letture di token di input memorizzati nella cache: circa $ 165.000 al prezzo API. A mano, penso che questo avrebbe richiesto circa un anno a 3 ingegneri con il contesto completo sulla base di codice, durante il quale non saremmo stati in grado di migliorare la compatibilità di Node.js, correggere bug, risolvere problemi di sicurezza o implementare nuove funzionalità. Non lo avremmo mai fatto. L'alternativa realistica era non fare nulla e continuare a correggere i bug all'inizio di questo post per sempre.
Questo è il limite massimo di ciò che è possibile fare oggi. Ho utilizzato una versione pre-release di Claude Fable 5, un modello di classe Mythos. I flussi di lavoro dinamici di Claude Code hanno mantenuto in funzione 64 Claude per 11 giorni (altrimenti avrei dovuto scrivere il mio cablaggio per farcela).
Il lavoro continua
Da quando abbiamo unito la porta Rust, abbiamo completato 11 cicli di analisi della sicurezza da parte di Claude Code Sicurezza e abbiamo affrontato i risultati.
Abbiamo anche aggiunto il fuzzing guidato dalla copertura 24 ore su 24, 7 giorni su 7, di ogni parser in Bun: JavaScript, TypeScript, JSX, CSS, JSON5, JSONC, TOML, YAML, Markdown, INI, Bun script di shell, intervalli semver, file .patch e colori CSS. Il fuzzer invia automaticamente i bug rilevati a Claude per inviare un PR che li riproduca e risolva, e gli umani revisionano i PR. Finora ha eseguito i nostri parser 100 miliardi di volte, il che ha portato a circa 15 PR.
Al momento della stesura di questo articolo, circa il 4% del codice Rust di Bun si trova all'interno di un blocco unsafe (~13.000 parole chiave unsafe su ~27.000 righe / ~780.000 righe) e il 78% di questi blocchi sono una singola riga: un puntatore proveniente da C++ o una chiamata a una libreria C. Mi aspetto che questo numero diminuisca nel tempo man mano che eseguiamo il refactoring da un port Zig fedele (che non aveva parole chiave unsafe greppable) a Rust idiomatico, ma continueremo a utilizzare librerie C e C++ come JavaScriptCore quindi avrà sempre più unsafe che progetti Rust puri.
Errori di trasferimento
L'obiettivo della riscrittura di Rust è la stabilità, ma sarebbe impossibile introdurre un cambiamento massiccio come questo e introdurre zero regressioni.
Questa riscrittura ha introdotto 19 regressioni conosciute, ognuna delle quali è stata corretta.
La maggior parte delle regressioni proveniva da codice sintatticamente identico in entrambi i linguaggi ma semanticamente diverso.
Effetto collaterale all'interno di debug_assert!
Questi due snippet sembrano simili ma si comportano diversamente. assert di Zig è una funzione, quindi il suo argomento viene eseguito in ogni build. debug_assert! di Rust è una macro, quindi nella release build l'intero expression viene cancellato, inclusa la chiamata insert_stale.
// Zig:
if (dev.framework.react_fast_refresh) |rfr| {
assert(try dev.client_graph.insertStale(rfr.import_source, false) == IncrementalGraph(.client).react_refresh_index);
}
// Rust:
if let Some(rfr) = &dev.framework.react_fast_refresh {
debug_assert!(dev.client_graph.insert_stale(&rfr.import_source, false)? == react_refresh_index);
}
insert_stale aggiunge un file al grafico di ricaricamento a caldo del server di sviluppo frontend. Nelle build di rilascio ha smesso di funzionare e HMR si è interrotto in alcuni casi per progetti con percorsi HTML che utilizzano React mentre un file ricaricato a caldo viene invalidato: Cannot destructure property 'isLikelyComponentType' of 'k'. Le build di debug hanno funzionato. #30678
Fette di lunghezza dispari
Bun (antecedente ai cast incorporati che supportano le slice) utilizzava @divTrunc e ignorava un byte dispari finale. bytemuck::cast_slice invece si lascia prendere dal panico. Blob.text() su un segno di ordine dei byte UTF-16 seguito da un numero dispari di byte ha smesso di restituire una stringa e ha mandato in panico il processo. Siamo tornati a ignorare il byte dispari: &buf[..buf.len() & !1]. #31188
Controlli dei limiti
Su macOS e Linux, abbiamo compilato il codice Zig di Bun con ReleaseFast, che rimuove i controlli dei limiti. Le build di rilascio di Rust le mantengono.
Bun inserisce i nomi di file lunghi in un elenco globale che si riversa nei blocchi di overflow. Il codice Zig originale dimensionava ogni blocco a count / 4 o 2048. La porta ha lasciato un segnaposto:
/// ... so use a nonzero stand-in until Phase B threads the
/// per-instantiation value through.
pub const BSS_OVERFLOW_BLOCK_SIZE: usize = 64;
Ciò ha abbassato il tetto da 8,4 milioni di nomi di file internati a 270.272, che i progetti reali hanno raggiunto, e ha reso raggiungibile un ptrs[4095] off-by-one che abbiamo portato da Zig. Rust si è lasciato prendere dal panico invece di scrivere oltre la fine. Anche Zig andrebbe nel panico in questo caso, se utilizzassimo ReleaseSafe (lo abbiamo fatto solo su Windows). #31503
comptime stringhe di formato
Output.pretty riscrive i marcatori di colore <r> e <d> in escape ANSI. In Zig, fmt è comptime, quindi i marcatori scompaiono prima che gli argomenti vengano sostituiti. Le funzioni Rust non hanno parametri comptime, quindi Output::pretty ha sempre e solo visto la stringa finita e ha riscritto anche i marcatori sugli argomenti.
// Zig:
pub inline fn pretty(comptime fmt: string, args: anytype) void;
Output.pretty("<r>{f}<r>", .{hyperlink});
// Rust:
pub fn pretty(payload: impl PrettyFmtInput);
Output::pretty(format_args!("<r>{}<r>", hyperlink));
bun update -i stampa i nomi dei pacchetti come collegamenti ipertestuali OSC 8, terminati con ESC \. Quella barra rovesciata si trova proprio prima di < del <r> finale, il parser del marcatore lo mangia e r viene stampato come testo.
In Rust deve essere una macro: bun_core::pretty!("<r>{}<r>", hyperlink). #30693
Bun è migliore in Rust
Finora, Bun v1.4.0 corregge 128 bug che si riproducono nella v1.3.14. Questi vanno dalle perdite di memoria agli arresti anomali al testo della guida con colori errati.
Utilizzo della memoria ridotto
Rust dispone di un potente strumento a livello di linguaggio per pulire la memoria: Drop. Quando viene implementato Drop, la funzione drop viene chiamata automaticamente ogni volta che il valore esce dall'ambito.
impl Drop for Bytes {
fn drop(&mut self) {
if !self.pinned.is_empty() {
JSC__JSValue__unpinArrayBuffer(self.pinned);
}
}
}
In Zig, defer può essere utilizzato per eseguire codice alla fine di un ambito:
const bytes: ArrayBuffer = try .fromPinned(global, value);
defer bytes.unpin();
In Zig, defer deve essere aggiunto a ogni singolo sito di chiamata che potrebbe necessitare di pulizia. È facile dimenticare di eseguire la pulizia (una perdita di memoria) o di eseguire il codice di pulizia due volte in un codice di gestione degli errori raramente raggiunto (un double-free). In Rust, Drop viene eseguito automaticamente quando il valore non è più accessibile, scambiando "nessun flusso di controllo nascosto" con la prevenzione di un comune fucile.
Drop ha corretto diverse perdite di memoria in Bun relative ai percorsi dei file nel codice di gestione degli errori.
Abbiamo corretto ogni perdita di memoria strumentale
Abbiamo migliorato l'integrazione di LeakSanitizer di Bun per tenere traccia di tutte le allocazioni di memoria del codice nativo.
Ecco un esempio: ogni chiamata Bun.build() in-process ha fatto perdere diversi megabyte di memoria: testo sorgente analizzato e tabelle di simboli AST che sono sopravvissute alla build a cui appartenevano.
// Bundle the same 60-module project 2,000 times in one process
for (let i = 0; i < 2_000; i++) {
await Bun.build({
entrypoints: ["./index.js"],
minify: true,
sourcemap: "external",
});
}
In Bun v1.3.14, ogni build perde circa 3 MB, per sempre: strumenti come i server di sviluppo che si raggruppano su ogni richiesta prima o poi esauriscono la memoria. In Bun v1.4.0, i livelli di memoria sono disattivati:
| Costruisce | Bun v1.3.14 | Bun v1.4.0 |
|---|---|---|
| 500 | 1.914 MB | 526MB |
| 1,000 | 3.506 MB | 586MB |
| 1,500 | 5.097 MB | 608MB |
| 2,000 | 6.745 MB | 609MB |
Un tentativo precedente di eseguire questa operazione in Zig non è stato unito perché la mancanza di un equivalente di Drop rendeva più difficile sentirsi sicuri nell'unione.
Dimensione binaria più piccola
Le modifiche iniziali nella riscrittura Rust hanno ridotto la dimensione binaria di 3,8 MB su Windows, 5,5 MB su macOS e 6,8 MB su Linux. Ciò è in gran parte dovuto al fatto che abbiamo utilizzato troppo comptime nel nostro codice Zig.
Dopo questa contrazione iniziale, il team ha esplorato ulteriori opportunità per la riduzione delle dimensioni binarie utilizzando ottimizzazioni del linker come Identical Code Folding, rimuovendo i dati inutilizzati da ICU e decomprimendo pigramente piccole parti di libicu con un dizionario zstd su richiesta.
In combinazione con la riscrittura di Rust, le modifiche all'ICU e la piegatura identica del codice, la dimensione binaria di Bun si riduce del ~20% su Linux e Windows.
| Versione | Piattaforma | Dimensione |
|---|---|---|
| Bun v1.4.0 (canarino) | Finestre | 76MB |
| Bun v1.3.14 | Finestre | 94MB |
| Bun v1.4.0 (canarino) | Linux | 70MB |
| Bun v1.3.14 | Linux | 88MB |
Utilizzo ridotto dello spazio nello stack
Il parser TOML e tutti gli altri parser a discesa ricorsiva in Bun (JSON, YAML, JavaScript, TypeScript e altri) ora utilizzano meno spazio nello stack.
Ciò ha causato alcuni errori nei test prima di unire la riscrittura Rust:
bun test v1.3.14-canary.1 (e99311e58)
.......
105 | });
106 |
107 | it("Bun.TOML.parse throws on deeply nested inline tables instead of crashing", () => {
108 | const depth = 25_000;
109 | const deepToml = "a = " + "{ b = ".repeat(depth) + "1" + " }".repeat(depth);
110 | expect(() => Bun.TOML.parse(deepToml)).toThrow(RangeError);
^
error: expect(received).toThrow(expected)
Expected constructor: RangeError
Received function did not throw
Received value: {
a: {
b: {
b: {
b: {
b: {
b: {
b: {
b: {
b: [Object ...],
},
},
},
},
},
},
},
},
}
at <anonymous> (/var/lib/buildkite-agent/build/test/js/bun/resolve/toml/toml.test.js:110:42)
✗ Bun.TOML.parse throws on deeply nested inline tables instead of crashing [2907.64ms]
Rust emette gli elementi intrinseci llvm.lifetime.start e llvm.lifetime.end di LLVM per le variabili dello stack quando non sono più in uso, consentendo a LLVM di riutilizzare gli slot dello spazio dello stack. Ciò consente a funzioni di grandi dimensioni con ambiti nidificati di utilizzare molto meno spazio nello stack.
In precedenza, abbiamo risolto manualmente un problema aperto refactoring di funzioni particolarmente grandi in molte funzioni più piccole.
2% - 5% più veloce
Rust supporta l'ottimizzazione del tempo di collegamento tra linguaggi diversi tra C/C++ e Rust, che consente l'integrazione tra linguaggi di programmazione (che bello!!).
Abbiamo confrontato Bun v1.3.14 con Bun v1.4.0 su Linux x64 (EC2, Xeon Platinum 8488C). Velocità effettiva HTTP misurata con oha rispetto ai server Hello-World, carichi di lavoro delle app misurati con hyperfine.
Throughput HTTP (richiesto/s, media di 3 round)
| server | Bun v1.3.14 | Bun v1.4.0 | Δ |
|---|---|---|---|
| Bun.serve | 169.6k | 177.7k | +4.8% |
| node:http | 103.8k | 108.5k | +4.5% |
| Elysia | 158.9k | 163.3k | +2.8% |
| express | 64.5k | 66.6k | +3.2% |
| fastify | 91.5k | 95.9k | +4.8% |
App/CLI (hyperfine)
| carico di lavoro | Bun v1.3.14 | Bun v1.4.0 | Δ |
|---|---|---|---|
| next build | 13.62 s | 13.03 s | +4.5% |
| vite build (tsc + vite) | 1.69 s | 1.65 s | +2.2% |
| tsc -b --force | 0.94 s | 0.89 s | +4.7% |
Produzione
Prisma ha lanciato la beta pubblica di Prisma Compute in seguito alla riscrittura Rust di Bun.
"Abbiamo riscontrato perdite di memoria e un pool di connessioni che non è stato possibile ripristinare dopo la messa in pausa e la ripresa di una VM. Quando è apparsa la riscrittura Rust, l'abbiamo testata rispetto alle stesse modalità di errore. Li ha gestiti perfettamente." - Alexey Orlenko
Claude Code v2.1.181 (rilasciato il 17 giugno) e versioni successive utilizzano il port Rust di Bun. L'avvio è diventato più veloce del 10% su Linux, ma per il resto quasi nessuno se ne è accorto. Noioso è positivo.
Spedizione
Bun v1.3.14 era l'ultima versione di Bun scritta in Zig. Bun v1.4.0 sarà la prima versione di Bun scritta in Rust. Ora è disponibile in Canary: segnala eventuali problemi riscontrati:
bun upgrade --canary
Manutenibilità
Per me e per il team, la nostra nuova codebase Rust sembra molto simile alla vecchia codebase Zig. Ad esempio, ecco uno snippet del codice Zig originale e del nuovo codice Rust:
pub fn canMergeSymbols(
scope: *Scope,
existing: Symbol.Kind,
new: Symbol.Kind,
comptime is_typescript_enabled: bool,
) SymbolMergeResult {
if (existing == .unbound) {
return .replace_with_new;
}
if (comptime is_typescript_enabled) {
// In TypeScript, imports are allowed to silently collide with symbols within
// the module. Presumably this is because the imports may be type-only:
//
// import {Foo} from 'bar'
// class Foo {}
//
if (existing == .import) {
return .replace_with_new;
}
// ...
}
// ...
}
pub fn can_merge_symbol_kinds<const IS_TYPESCRIPT_ENABLED: bool>(
scope_kind: Kind,
existing: symbol::Kind,
new: symbol::Kind,
) -> SymbolMergeResult {
if existing == symbol::Kind::Unbound {
return SymbolMergeResult::ReplaceWithNew;
}
if IS_TYPESCRIPT_ENABLED {
// In TypeScript, imports are allowed to silently collide with symbols within
// the module. Presumably this is because the imports may be type-only:
//
// import {Foo} from 'bar'
// class Foo {}
//
if existing == symbol::Kind::Import {
return SymbolMergeResult::ReplaceWithNew;
}
// ...
}
// ...
}
Chiunque comprenda il codice Zig originale comprende il codice Rust tradotto meccanicamente. Ho rivisto il PR di riscrittura Rust originale controllando che gli agenti di revisione del codice dell'avversario rilevassero correttamente le discrepanze tra il codice Zig e il codice Rust, che assicurassero che la guida al porting e la guida a vita fossero seguite e anche leggendo manualmente gran parte del codice fianco a fianco con Zig vs Rust.
Quali sono le prospettive successive
Bun v1.4 rende Bun più veloce, più piccolo, utilizza meno memoria e offre al team strumenti incredibilmente potenti per migliorare sistematicamente la stabilità in futuro: il controllo dei prestiti di Rust, Miri (che viene eseguito per una parte crescente di codice in CI), LeakSanitizer e fuzzing guidato dalla copertura 24 ore su 24, 7 giorni su 7 per i parser. C'è ancora altro da refactoring, ma le cose sono iniziate alla grande.
Questa riscrittura Rust avrebbe richiesto un anno di lavoro a un team di ingegneri con contesto completo sulla base di codice. Con 1 ingegnere che utilizza Fable e monitora attentamente Claude Code, siamo passati dall'inizio al 100% della suite di test superando tutte le piattaforme in 11 giorni.
Un ingegnere può fare molto di più oggi rispetto a un anno fa.
