Il diario di costruzione
· changesUna voce per ogni fase del progetto fino all’ultimo aggiornamento del sito, dalla più recente; quel che si è chiuso dopo è nella roadmap del repository. I resoconti completi, con decisioni, revisioni e problemi incontrati, sono nel repository, che è aperto in lettura.
- Il controllo che non finiva mai Ogni issue ancora aperta, difetti, compiti e funzionalità in attesa, è stata rimessa alla prova contro il compilatore di oggi e ha ricevuto una risposta scritta: alcune erano state risolte strada facendo, alcune non valevano più, qualcuna è stata sistemata sul momento. Una era un controllo che non riusciva a finire. Riscrive ogni programma con un commento in ogni punto dove può stare e una parentesi spezzata dopo ogni parola, e chiede se il formattatore, lo strumento che impagina un programma nell’unico modo che il linguaggio ammette, lo conserva. Su un file di prova, una sola istruzione profonda duemila parentesi, servivano più di due giorni di calcolo. La colpa era del formattatore, che a ogni livello di una catena ricalcolava tutto quello che stava sotto. Ora che calcola ogni parte una volta sola, il file è stato giudicato per intero in meno di cinque ore, e il giro completo, più di quattro milioni di varianti, è finito senza un solo rifiuto. Ogni programma del repository esce ancora identico a prima, byte per byte. Il formattatore, fra i tredici verbi
- La tesi messa alla prova Per la prima volta la tesi su cui poggia Heroes è stata messa alla prova con i modelli al lavoro, le intelligenze artificiali che scrivono codice: quaranta compiti scritti e sigillati dall’autore, ognuno una trappola, giudicati con le regole proprie di Heroes accese e spente, e una previsione resa pubblica prima di ogni risposta. Nessun modello ha visto un compito prima della prova: l’autore li ha scritti in una cartella vietata agli strumenti dell’assistente, e li ha lanciati dal suo terminale. Strada facendo si è scoperto che lo strumento teneva risposte di un modello mai interpellato, e il collaudo è stato rifatto: senza la specifica, nessun modello ha scritto un programma corretto. Il risultato si legge solo contro la previsione, e il sito lo dirà alla soglia di pubblicazione, quando si annuncia una cosa finita. Che cosa si misura, e come
- Il seed trova un testimone Il seed, l’unico file C da cui una macchina senza Heroes costruisce il compilatore, aveva sempre dimostrato soltanto di riprodurre se stesso, cosa che saprebbe fare anche un seed bugiardo. Ora un secondo compilatore, indipendente, garantisce per lui. Il primo compilatore del progetto, scritto in Rust e archiviato, è stato ricostruito e ha riscritto il primo seed identico byte per byte; ognuno delle decine di seed successivi è stato ricostruito dal precedente. Al primo passo, il compilatore nato dal seed dell’archivio lavorava ancora dopo sette ore; quello in Rust ci ha messo meno di un secondo. Ora anche gcc, un secondo compilatore C, costruisce il seed accanto a clang, quello che Heroes usava già. Ogni nuovo seed dice per quale strada lo ricostruisce il precedente, e la CI, i controlli automatici su ogni modifica, la ripercorre. Come verificare che compili se stesso
-
Letto, e mai scritto
Una funzione che deve solo leggere ciò che tiene chi la chiama ora può dirlo:
function peek(l: Lex lent)legge il valore del chiamante lì dov’è, non lo scrive mai, e si chiama semplicementepeek(l). Prima, chi leggeva soltanto un record con dentro un lock, un blocco che il linguaggio non lascia copiare, aveva due grafie, entrambe false: una copia, rifiutata, oppure@, che dichiara una modifica. Passato a una libreria C, il valore ora parte con l’avviso che il C può solo leggerlo. I lettori con la sola specifica, prima che la parola esistesse, ci arrivavano al secondo tentativo, dieci volte su dieci; con la parola, nove su dieci al primo. E il nuovo testo della specifica è costato quanto aveva previsto il giudice che la pesa, fino all’ultimo token, l’unità in cui un modello legge il testo. La regola, nella specifica -
Da che cosa è stato costruito
Ogni programma che Heroes costruisce ora porta un’impronta di ciò da cui è nato: i suoi sorgenti, la sua libreria e il suo runtime, il codice di supporto che entra in ogni programma.
heroes --versionstampa quella del compilatore sulla seconda riga. Il seed, riscritto a ogni giro di correzioni, occupava più di due terzi della storia del repository. Metà del suo peso erano righe che ripetevano una posizione già data: ora sono unite, e il seed si riscrive solo quando il compilatore nato da lui non sa più costruire quello di oggi, e a ogni release, ogni versione che il progetto pubblica. Il giorno dell’arrivo, il controllo che giudicava un compilatore dalla data del file ha rifiutato proprio il compilatore che serviva; ora chiede al compilatore da che cosa è nato, e il compilatore risponde. Il seed, e l’impronta -
Il valore che calcola il C
Quando l’header di una libreria C, il file in cui descrive ciò che offre, definisce una costante, il compilatore ora prende il valore che calcola il C stesso, facendo girare un piccolo pezzo del programma sotto i controlli di clang. Ogni strada che non faceva girare il codice del programma sbagliava in un senso o nell’altro, su decine di migliaia di costanti vere e tre versioni di clang; quella che lo fa girare le ha azzeccate tutte. Ogni buco trovato stava nel codice nuovo del compilatore, non nei programmi, e l’ha trovato chi attaccava il codice così com’era, mai i test automatici. Un record C con lunghi array si costruisce nominando i campi che contano e chiudendo con
rest: zero; un record di cinque campi che descrive la macchina chiedeva più di mille zeri scritti a mano. Costanti e record dal C -
Un programma fermo mostra quello che hai scritto
Ferma un programma Heroes in un debugger, lo strumento che mette in pausa un programma mentre gira per guardarci dentro, e ora vedi quello che hai scritto: le tue variabili, e i loro valori in Heroes, una lista come lista e un fallimento come fallimento. Tutto ciò che aveva inventato il compilatore è sparito, una decina di nomi dove prima ce n’erano più di cento. Solo i nomi hanno tenuto il prefisso del compilatore, perché un nome nudo nel C può prenderselo l’header di qualunque libreria, e su una macchina è successo: un programma con una variabile chiamata
linuxsi compilava sul Mac ed era rifiutato su Linux. Ogni correzione al confine con il C, quella notte, ha trovato la successiva, sei di fila, e ognuna ha cambiato chi il messaggio incolpa, mai che cosa rifiuta. Perché il compilatore scrive C -
Ogni voce diventa una scheda pubblica
Ogni difetto, decisione e compito del progetto ora è una issue, una scheda pubblica su GitHub, il sito dove vive il suo codice: più di duemila, importate nell’ordine in cui sono nate, ognuna con le sue date. Poi la cartella da cui venivano è uscita dal repository. Su richiesta dell’autore, dove qualcuno fuori dal progetto c’era arrivato prima, la decisione lo nomina, come la pagina dei ringraziamenti. L’ultima lettura ha trovato collegamenti tra le issue che il panel, i cinque giudici che esaminano ogni modifica al linguaggio, aveva ordinato e nessuno aveva fatto. Il primo difetto chiamato con il numero di GitHub era del compilatore: due moduli con un record
Heldciascuno, passato a una funzione scritta per qualunque tipo, superavano il controllo e non si compilavano, con un solo nome in C per le due copie. Qualcuno c’era arrivato prima -
Una variabile che cambia ha un simbolo tutto suo
Una variabile il cui valore può cambiare, una cella, ora si dichiara
v @= 0e prende il tipo dal suo valore;v @ v + 1la cambia, e una cella che niente cambia mai è rifiutata, con una correzione che scrive=. La domanda e la grafia erano dell’autore: perché una cella deve portarsi dietro il tipo e un nome fisso no? Perché@faceva due lavori, e solo il tipo li distingueva. La vecchia rigav: i64 @ 0è rifiutata con una correzione che il compilatore applica da solo, e quella correzione ha riscritto 1.181 file in un solo commit. La riscrittura ha sbagliato nell’unico posto che il compilatore non vede, il testo dentro la stringa di un test, e dodici test del compilatore sono falliti tutti insieme. Come si dichiara una cella - La lista che non si prosciugava Le tre parole scritte sugli handle di una libreria C, gli indirizzi che consegna a un programma per cose che possiede lei, sono diventate cinque, e il runtime ora numera ogni vita che il C consegna. Un indirizzo non è un’identità: il C può liberarne uno e ridarlo a qualcos’altro. Prima, un handle scaduto allo stesso indirizzo di uno vivo veniva preso per quello, e un programma corretto poteva abortire dieci volte su dieci su tre piattaforme. Poi ogni correzione, provata sui casi accanto, ha trovato il difetto successivo, più di venti dopo la prima stesura del diario della milestone. La lista non si prosciugava, ed era il metodo che funzionava. L’autore l’ha fermata a mano, e il controllo che avrebbe rifiutato la chiusura ha imparato a dirlo. Le cinque parole, nella specifica
-
La parola che non arrivava alla chiamata
Una funzione C che riceve un indirizzo nella memoria del programma ora si presume che lo tenga, salvo che la dichiarazione dica
lent: il panel aveva rifiutato questa regola, e l’autore l’ha scelta per principio, purché il costo fosse misurato, meno di quaranta miliardesimi di secondo a chiamata. Si era partiti da quanto può leggere il C: con un campo di otto byte e un conteggio di 64, il C sovrascriveva in silenzio il campo accanto; ora quella misura si dichiara e controlla. Dove nessuna parola arriva, perché il C tiene l’indirizzo a una chiamata e lo libera in un’altra che non lo riceve, il programma che muore dice quanti prestiti erano aperti. La prima versione incolpava il C: l’ingegnere del compilatore e il pragmatico del C hanno posto il veto: il controllo fallito di una libreria sembra uguale. Prestare al C -
Il muro era per metà un miraggio
Il testo che il C scrive in un campo di byte a misura fissa, come il nome della macchina che ci scrive il sistema, ora si può stampare:
f.validated_bytes()lo legge, oppure dice che non è testo. L’aveva chiesto l’autore, quando una seduta aveva visto che nessuno poteva stamparlo. Tutta la differenza era una riga: la funzione che già leggeva testo dal C cercava uno zero finale, che un campo fisso può non avere. L’altro muro, costruire un record che il C riempia, non c’era: lo si chiede alla libreria, e i test del progetto lo facevano in sette punti mentre tre sedute discutevano come farlo. Le istruzioni dell’assistente alle sedute avevano detto tre volte che al compilatore mancavano cose che aveva; ora una frase che dice che qualcosa manca si verifica, o parte come domanda. Il testo che arriva dal C -
La quarta macchina ha smentito la sua previsione
Heroes ora si costruisce e gira su un quarto tipo di macchina, Linux su processori ARM, e per prima cosa ha smentito la sua stessa previsione. La previsione diceva che il
chardel C, il suo numero da un byte, su ARM non scende mai sotto zero. Su Linux è così; ma anche questo Mac è ARM, e il suocharci scende. La differenza è del sistema, non del processore, e su questo punto tre macchine erano sempre state una. L’unico test capace di vederla diceva da sempre, in un commento, che lì ilcharha il segno. Sette strade discusse: sei descrivevano la differenza, e la vincitrice la toglie con un solo flag, un’opzione data a clang,-fsigned-char, e il segno è uguale ovunque. Linux stesso aveva preso il flag speculare, per la stessa ragione. Costruirlo sulla tua macchina -
Un buco che non era dove diceva il nome
La milestone cercava programmi che
heroes checkaccetta eheroes buildrifiuta, e ha trovato che una sola faccia su quattro era quel divario, ed era già chiusa da una seduta tenuta lo stesso giorno di quella che l’aveva nominata. Il buco vero era una discesa nel codice che si arrendeva dopo sedici livelli, difesa da un argomento vero: la guardia a runtime esiste e scatta. All’argomento mancava una frase, e cioè che una guardia che autorizza un silenzio a compile-time è una guardia su cui il compilatore si appoggia per sbagliare in silenzio. Dieci difetti aperti dalle sue stesse sedute e dieci chiusi, uno aperto dall’autore chiedendo perché la CI fosse rossa; e la specifica è uscita più corta di com’era entrata, con due regole in più. Cosa il compilatore ferma -
Tre parole per ciò che si prende in prestito dal C
Un binding ora dice, su ogni funzione che tocca un handle, una di tre cose:
acquirese chi lo riprende,consumes,borrows. Nessuna delle tre viene dedotta dal compilatore. La forma che deduce da sola è caduta quando il pragmatico del C ha aperto diciannove header di librerie vere: in dodici la stessa libreria consegna un handle da tenere con una funzione e da guardare con un’altra, senza dirlo. La grafia che nomina chi rilascia l’aveva proposta il lettore, che non aveva modo di pesarla, ed è risultata anche la meno cara. E la regola per cui ogni strumento che rilegge un programma va istruito su una forma nuova è scattata tre volte, una su un difetto che nessuno aveva visto. Gli handle e le tre parole - L’etichetta va sulla consegna, non sulla cosa Scrivere sul tipo di un handle chi lo chiude, così che il compilatore chiuda da solo: rifiutato, perché la stessa connessione una funzione la consegna e un’altra la mostra soltanto, e un’etichetta sul tipo non sa quale delle due l’ha data. Il pragmatico del C l’ha compilato: un programma che legge tre righe ne leggeva zero e diceva che era andato tutto bene. L’ingegnere del compilatore ha posto l’unico veto formale della seduta. E il conteggio fatto prima di discutere ha trovato, di passaggio, che confrontare due handle rispondeva sempre sì: la riga più comune di chi prende in prestito qualcosa dal C, quella che chiede se ha ricevuto qualcosa, non aveva mai funzionato. Il no, per intero
-
Il nome della libreria diventa il tipo
Un puntatore che una libreria C ti consegna può portare il nome che la libreria gli dà:
record Db tag sqlite3. Consegnane uno dove ne serve un altro e il compilatore ti ferma su quella riga. Tre difetti aperti da tre persone in tre giorni diversi erano una sola domanda: che cosa non dice un indirizzo nudo? Non dice a che cosa punta, quale è, e di chi è. Lo strumento costruito prima della correzione diceva che metà dei casi era già presa: ne prendeva zero, e la metà erano accidenti del test. Alla fine una seduta convocata su una parola ha tenuto la parola, e il critico di completezza ha trovato una conversione nascosta al confine e ha svuotato gli ultimi posti degli esempi dove un indirizzo nudo usciva allo scoperto. Gli handle - Le ragioni erano marcite sotto le risposte Ogni voce della lista delle cose rimandate ha ricevuto una data e un verdetto: entra, è rifiutata per sempre, oppure aspetta con una condizione controllabile. Nove voci, nove sedute, e nove volte la risposta era buona e la ragione scritta sotto era marcita. Una aveva un orologio già scaduto; una diceva che una cosa non si poteva fare e otto programmi l’hanno fatta quel pomeriggio; una descriveva un pezzo del compilatore che questo progetto non ha mai avuto; una era contraddetta dallo stesso documento, quaranta pagine prima. La decisione che ogni voce rimandata riceva un verdetto è dell’autore, e cinque voci sono diventate no in un solo giorno. Rimandato non è rifiutato
- Ogni soffitto dice da che parte può muoversi L’autore ha detto che continuava a sbattere la testa contro i soffitti e ha chiesto quali avessero ancora senso. Quasi tutti erano fissati all’altezza esatta della cosa misurata, il giorno in cui qualcuno l’aveva misurata. Uno era peggio degli altri. Il regolamento che l’assistente legge prima di ogni lavoro veniva pesato con una bilancia presa in prestito, e con quella stava sotto il limite per nove unità; con la bilancia che il lettore usa davvero era sopra di milleottocento, e ogni strumento diceva che andava tutto bene. Ora accanto a ogni numero c’è scritto in che direzione può muoversi, e qualcosa se ne accorge quando avrebbe dovuto muoversi e non l’ha fatto. Il repository
-
Il nome di una cosa non è più un testo scritto a mano
Un programma non può chiedere a un valore di che cosa è fatto: tre no, e ogni volta la ragione scritta era falsa, quindi le ragioni sono state sostituite con ragioni misurabili. Poi è arrivata la parte non prevista. Un programma che manda fuori dei dati doveva scrivere i nomi dei propri campi come testo, e una lettera sbagliata usciva nel mondo senza che niente si lamentasse. Quattro dei cinque seggi hanno inventato qualcosa che non era in lista, a coppie, senza vedersi:
Point::xè il nome del campo, e il compilatore sa se esiste. Il lettore e l’ingegnere l’hanno proposto nella stessa ora con due grafie, e l’autore ha scelto la grafia che la macchina non aveva già costruito, per una ragione di lettura. Con i nomi come testo, quattro refusi e nessuno preso; con i nomi come nomi, cinque su cinque. Il no alla reflection -
La grammatica è andata dove stava la prosa
L’autore ha chiesto il linguaggio scritto in forma formale, in un file suo. Cinque seggi l’hanno guardato da cinque direzioni e ognuno ha rotto la stessa metà: quella che sarebbe stata copiata a mano. Il lettore, che poteva leggere solo la specifica, ha trovato che diceva già la cosa che la proposta voleva sistemare; lo storico è tornato con ogni tentativo di quel genere, in ogni altro linguaggio, abbandonato. Così ogni regola di sintassi sta ora nella sezione della specifica che la spiega, nella notazione di Wirth, e la parte che una macchina può ricavare da sé la ricava
heroes grammar, ogni volta che glielo chiedi. La specifica - I registri non sono più un file ciascuno L’autore ha chiesto se due lavori potessero correre insieme, e la risposta è stata una misura: due sessioni non si incontrano mai nel compilatore, si incontrano nei registri, sulla riga che entrambe devono scrivere. Così ogni voce di un registro è un file, e il vecchio file resta al suo posto come mappa, riga per riga, perché centosessantuno citazioni puntavano a numeri di riga che nessuno poteva controllare. Il punto cieco che il progetto aveva documentato come incorreggibile è diventato un controllo che gira a ogni verifica. Il repository
-
Il tipo di
folddice quale dei due è il mucchio La funzione che combina due cose riceve l’accumulatore e l’elemento, e se sono dello stesso tipo entrambi gli ordini compilano e il programma risponde altro. Ora il tipo dice quale è quale, e il compilatore controlla che la funzione che passi sia d’accordo. Il panel aveva detto che era il prezzo di una regola già scritta; l’autore ha detto di chiudere comunque. La versione gentile prendeva due dei sette casi veri del progetto, quella severa sette, al prezzo che chiunque scriva una funzione perfoldchiami i due parametriacceitem: l’autore ha letto i due numeri e ha scelto la severa. Un lettore che non aveva mai visto il progetto ha scritto quelle due parole tre volte in tre programmi senza fermarsi a sceglierle. Le funzioni come valori - Un tipo funzione porta i nomi che si confondono La metà del buco che il panel aveva prezzato e rifiutato, l’autore ha detto di costruirla comunque: il tipo di una funzione porta i nomi degli argomenti che si possono scambiare, e cambiarne uno lo rende un altro tipo. Poi il panel ha giudicato una cosa già esistente e tre seggi su cinque l’hanno rimandata indietro: il guardiano della specifica per una frase con una lettura falsa, il pragmatico del C con una callback che ignora un argomento, come fanno le callback tutto il giorno, e l’ingegnere per due ragioni fuori dal disegno, l’albero che non compilava e il prezzo contato con il righello sbagliato. Con quello giusto sono quattrocentotrentadue righe contro un bilancio di circa centocinquanta, e il numero è stato messo davanti all’autore. Il classico scambio, attraverso una funzione tenuta in una variabile, è passato da uno preso su sei a sei su sei. Le funzioni come valori
- Tre vie rifiutate da tre seggi, e la quarta costruita in venticinque righe Un’etichetta scritta in una chiamata fatta attraverso una funzione tenuta in una variabile è rifiutata: la funzione non ha nomi da confrontare. Nella seduta è successa una cosa mai vista: tre delle quattro vie d’uscita sono state rifiutate, ognuna da un seggio diverso e nessuna sul costo. Il guardiano della specifica ha rifiutato una frase perché un programma in esecuzione la smentiva; il pragmatico del C ha rifiutato una regola che avrebbe cancellato un programma funzionante, portando tre misure in cui lo stesso errore arrivava alla memoria; l’ingegnere ha rifiutato la correzione elegante perché non era una correzione ma una ricostruzione di undici stanze della casa. Poi lo stesso ingegnere ha costruito la via che nessuno aveva elencato, e ha chiuso metà del buco. L’altra metà è la cartolina sopra. Le funzioni come valori
- La regola dei nomi arriva alle funzioni predefinite Se due argomenti di una funzione sono dello stesso tipo, devi dire quale è quale. Il compilatore lo imponeva su tutto ciò che scriveva l’autore e su niente di ciò che forniva da sé. Lo ha trovato un lettore che aveva solo la specifica, scrivendo la stessa chiamata in due modi e non venendo ripreso per nessuno dei due. Lo strumento che imita l’errore classico, scambiare due argomenti dello stesso tipo, produceva trecentosessantasette programmi che il compilatore accettava e non doveva: centocinquantanove erano questa chiamata. Dopo la correzione ne restavano settantacinque, e nessuno era lei. Record e funzioni
- Tredici numeri sopra tredici porte L’autore ha letto la descrizione del linguaggio e ha detto che sembrava una stanza che nessuno aveva mai messo in ordine: ogni regola c’era, ognuna dov’era caduta. Il riordino è costato meno di niente. Venti frasi che dicevano la stessa cosa due volte sono diventate una, e il documento è uscito più leggero, con tredici sezioni numerate nell’ordine che il comitato di Naur scelse e Wirth tenne per quarant’anni. E perché l’autore l’ha chiesto, sei lettori che non avevano visto né il piano né il repository hanno ricevuto i due testi senza etichetta: hanno trovato tre aggiunte, tutte previste, niente di mancante, e una frase ambigua nel testo vecchio. La specifica
-
Testo con dei buchi dentro
Una stringa ora può portare dei valori:
f"line {n}: {word}"scrive ogni buco come lo scriverebbeto_str. In un buco può stare qualsiasi espressione,{{scrive una graffa e una}da sola resta una graffa, e una stringa semplice resta quella di prima. Il compilatore rifiuta un buco il cui valore non ha un testo, e la sua nota dice cosa scrivere al suo posto. L’esempio -
La decisione sull’interpolazione
Tre conteggi hanno deciso come una stringa possa contenere un valore, e i conteggi hanno corretto due volte la prima ipotesi. Ammettere qualsiasi espressione costa meno parole nella specifica che ammettere solo un nome, e la graffa lavorava già nelle stringhe del compilatore stesso: una stringa con dei buchi si annuncia con una
f, e una stringa normale resta com’era. La specifica -
Byte che una libreria C può tenere
Un programma può passare a una libreria C un testo che quella terrà, e dire quando ha finito:
x: cstr @= s.lease(), poiend_lease(@x). Ogni regola proposta per vietare questi programmi è stata misurata e rifiutata a sua volta; a chiudere la falla è stato un modo per dirlo, non un divieto. Un lease è un prestito che finisce quando lo dici tu; uno che nessuno chiude ferma il programma quandomaintermina, dicendo quanti ne restano aperti. Il confine con il C -
Una stringa prestata resta dove è prestata
Una stringa prestata al C con
.cstr()può stare solo come argomento della chiamata che la legge. Tenuta altrove, sopravviveva ai byte a cui puntava e il programma leggeva memoria liberata; il compilatore ora rifiuta quella forma, con la sua ragione, prima che qualcosa giri. Il confine con il C - Una nota che nessuno aveva contato Le closure, funzioni che si portano dietro le variabili intorno, non entrano in Heroes. Una nota le dava per facili dalla prima settimana del progetto; al momento di costruirle, per prima cosa si è contato. Le funzioni di una riga che avrebbero eliminato: una ventina, tutte negli esempi, nessuna nel compilatore. Il costo: centinaia di punti da cambiare in decine di file. Il lettore, che vede solo la specifica, ha detto che a fermare chi legge sono quattro frasi mancanti sulle funzioni che il linguaggio ha già, e su indicazione dell’autore una seconda seduta le ha guardate: il tipo di un filtro è identico che tenga o che scarti, e la specifica ha guadagnato una trentina di parole, che dicono anche quale metà tiene un filtro. Lo storico ha trovato quattro linguaggi che le rifiutano apposta, uno dal 1988. Il no, per intero
-
Il modo educato di perdere un fallimento
Scrivere
_ =davanti a una chiamata che può fallire ora ferma la compilazione. Insieme al valore se ne andava la notizia che era fallita, e il programma finiva con il numero che vuol dire che è andato tutto bene. L’autore aveva già deciso che andava rifiutato; restava da capire dove mettere il muro. Il primo muro aveva una porta, e uno più largo insegnava un modo per aggirarlo che sconfiggeva anche la regola: così il muro sta dove funziona, e la specifica dice che cosa passa ancora. Nessuna correzione è offerta come sicura: la stessa riga corretta in tre modi stampa 1, stampa 9 o ferma il programma. Più di duecento errori proprio di questo tipo sono stati piantati in programmi veri, e sono stati rifiutati tutti, più di quaranta solo dalle regole proprie di Heroes. Il fallimento è un valore - Il repository è aperto Il codice del compilatore, la specifica e tutti i registri del progetto sono ora leggibili da chiunque. La licenza è Apache 2.0, quindi il codice si può usare, modificare e riutilizzare. Domande e segnalazioni di problemi sono benvenute; le modifiche inviate non vengono accettate, e il repository spiega perché in un paragrafo. Il repository
-
Liberare le stringhe restituite dal C
Una dichiarazione
externpuò indicare quale funzione libera una stringa allocata da una libreria C. Il compilatore la chiama automaticamente. La modifica ha anche fatto emergere un conteggio errato delle funzioni usate nell’esempio SQLite. L’esempio SQLite - Controllare lo spazio dei thread I thread avviati da Heroes ora segnalano quando esauriscono lo spazio disponibile per le chiamate di funzione. Un revisore ha verificato che un thread creato da una libreria C viene invece fermato da un controllo precedente, che nomina la funzione Heroes coinvolta. Come usare i thread
- Memoria separata per ogni thread Ogni thread Heroes, un’operazione eseguita in parallelo, gestisce memoria separata. La revisione ha individuato un controllo mancante nelle callback, le funzioni richiamate dal C: consentiva a una lista di attraversare il collegamento con C senza essere segnalata. Gli esempi con thread
-
Passare funzioni Heroes alle librerie C
Puoi passare una funzione Heroes a
atexitopthread_create. Le callback richieste daqsortesignalnon sono ancora supportate. Il compilatore segnala il limite sulla dichiarazione. Un revisore ha esaminato l’header completo di SQLite e ha individuato ostacoli diversi da quelli emersi nei pochi esempi iniziali. Il confine con il C - Esempi più grandi per verificare il compilatore Sono stati aggiunti programmi più complessi per coprire la distanza tra gli esempi piccoli e il compilatore. Due revisori hanno respinto l’ottimizzazione proposta. Uno di loro ha poi individuato un uso eccessivo di memoria per valori temporanei che la proposta non considerava. I programmi
- Un errore nel passaggio degli elementi di un array Un commento descriveva come impossibile un crash che si poteva riprodurre con una sola riga. Passare un elemento di un array a una funzione che lo modifica forniva al C l’indirizzo dell’intero array. Gli esempi coprivano i campi dei record, ma non questo caso. Che cosa ferma il compilatore
- Documentare i moduli Alla documentazione mancava una spiegazione di come organizzare i file di un programma. È stato aggiunto il capitolo sui moduli. Leggi il capitolo
- Ampliare gli esempi Nuovi programmi sono stati scelti per verificare più parti del linguaggio. Il primo ha individuato un difetto: superava il controllo dei tipi, ma la compilazione falliva con un errore interno. I capitoli
- Organizzare i sorgenti in cartelle I file del compilatore sono stati distribuiti in cartelle. I lunghi prefissi nei nomi dei file sono stati sostituiti dalla struttura delle cartelle, rendendo i nomi più brevi. Il compilatore nella sua lingua
- Usare percorsi nei moduli I moduli possono essere cercati in sottocartelle, usando un percorso. Prima tutti i file dovevano trovarsi nella stessa cartella. Il panel dei giudici ha esaminato le nuove regole in più revisioni. I moduli e i file
- Eseguire comandi senza passare dalla shell Il compilatore avviava gli strumenti costruendo una stringa da passare alla shell, il programma che interpreta i comandi. Su Windows le stesse opzioni venivano interpretate diversamente e creavano una cartella con il nome sbagliato. Il problema ha portato a passare gli argomenti direttamente al processo. Perché il linguaggio ha questa forma
- Compilare un modulo alla volta La compilazione generava un unico file C molto grande anche dopo una piccola modifica. La generazione è stata divisa per modulo, così i risultati delle parti non modificate possono essere riutilizzati. Un file C per modulo
- Archiviare il primo compilatore Il primo compilatore è stato archiviato. Durante il lavoro è emerso che conteneva anche diversi strumenti usati per misurare e controllare il progetto. Il compilatore attuale
- Scrivere gli strumenti di test in Heroes Gli strumenti che verificano il compilatore sono stati riscritti in Heroes. Eseguono comandi, leggono i risultati e li confrontano con quelli attesi, riproducendo l’uso del compilatore da parte di una persona.
- Distribuire il compilatore come file C Per ottenere il compilatore basta compilare con clang un file C generato. Il repository contiene questo file, così anche un computer senza Heroes può costruire il compilatore.
- Il compilatore scritto in Heroes Il compilatore è stato convertito da Rust a Heroes, insieme ai suoi test. Compilando i propri sorgenti ha individuato difetti sia nella nuova versione sia in quella precedente. Gli esempi e i test con risultati attesi hanno verificato il passaggio. Come verificare il compilatore
- Convertire il primo modulo La prima prova consisteva nel convertire un file e registrare i limiti incontrati. Il limite previsto era già stato eliminato da un’altra modifica. Il lexer, che suddivide il sorgente in elementi riconoscibili, ha quindi funzionato senza l’ostacolo atteso.
- Completare il supporto alle strutture C Più dichiarazioni C sono diventate utilizzabili da Heroes. Il lavoro ha corretto rifiuti del compilatore che impedivano di usare forme già previste dal linguaggio.
- Passare strutture alle funzioni C Le strutture C raggruppano più campi in un valore, ma Heroes non riusciva ancora a passarle alle funzioni. Questa limitazione impediva di usare molte operazioni di una libreria reale.
- Verificare i parametri delle funzioni C Il compilatore controllava i risultati delle funzioni C, ma non i loro parametri. La verifica delle dichiarazioni è stata estesa anche ai tipi in ingresso.
- Trovare difetti con programmi completi I primi programmi completi hanno individuato nuovi difetti del compilatore. Comprendevano un lettore JSON, un convertitore Markdown, un foglio di calcolo, un risolutore di labirinti, un assemblatore, un analizzatore di log e un gioco.
- Interi di dimensioni diverse Heroes ha introdotto otto tipi di interi con dimensioni esplicite. La parola int viene rifiutata perché non specifica quale tipo usare. La scelta ha richiesto di rivedere un limite iniziale del progetto.
- Rendere esplicite le basi numeriche Un revisore ha scritto 0700 per indicare i permessi di un file e ha chiesto come venisse interpretato. La domanda ha evidenziato la necessità di regole chiare per distinguere le basi numeriche.
- Usare le costanti degli header C Le opzioni delle librerie C possono essere indicate per nome. Prima i programmi dovevano riportarne a mano il valore numerico. Leggere il valore dall’header elimina quella duplicazione.
- Confrontare le dichiarazioni con gli header Un esempio restituiva 1 dove era atteso 1.41: il controllo delle dichiarazioni C aveva un difetto. La verifica deve confrontare la dichiarazione Heroes con quella dell’header reale tramite clang.
- Definire i nomi dei moduli I programmi possono essere suddivisi in file. Sono state definite le regole per i nomi in conflitto e per i moduli importati ma non usati.
- Completare funzioni generiche e operazioni di base Le funzioni generiche e le operazioni di base sono state completate. Le questioni ancora aperte sono state verificate con il compilatore, comprese quelle sulla rappresentazione dei tipi durante l’esecuzione.
- Mappe e risultati che possono fallire L’implementazione delle mappe dipendeva dai valori che rappresentano un risultato o un errore. Cercare una chiave può infatti restituire il valore associato oppure un errore se la chiave manca.
- Rappresentare dati ricorsivi Heroes può rappresentare un albero di espressioni senza riferimenti modificabili tra i valori. Ogni nodo contiene i propri figli attraverso un array.
- Gestire la memoria delle stringhe Il compilatore ha introdotto il conteggio dei riferimenti per liberare la memoria. I controlli hanno messo in discussione la regola iniziale, che prevedeva di rilasciare ogni stringa alla fine del blocco che l’aveva creata.
- Produrre il primo eseguibile Un programma Heroes è stato compilato in un eseguibile nativo. Si è discusso anche di quale codice di uscita usare quando il programma è valido ma il compilatore non supporta ancora tutto ciò che serve.
- Scegliere la rappresentazione interna È stata definita la forma intermedia con cui il compilatore rappresenta un programma. Le indicazioni di LLVM hanno portato a usare inizialmente posizioni in memoria con letture e scritture, lasciando a un passaggio successivo le ottimizzazioni.
- Misurare i primi controlli I controlli su nomi, tipi e casi mancanti sono stati provati introducendo errori in programmi funzionanti. Il confronto con i controlli specifici disattivati ha permesso di misurare l’effetto delle regole di Heroes.
- Associare i nomi alle dichiarazioni Il controllo dei nomi ha rifiutato due dichiarazioni in un programma calcolatrice. Il programma definiva map e fold, ma entrambi i nomi erano già riservati dal linguaggio.
-
Analizzare e formattare il codice
Scrivere il parser, che riconosce la struttura del codice, ha rivelato una contraddizione nella specifica.
fnera sia una parola vietata sia la forma prevista per un tipo funzione. Di conseguenza non si potevano dichiarare parametri di quel tipo. - Suddividere il sorgente in token Il primo componente del compilatore suddivide il testo sorgente in token, gli elementi che il compilatore riconosce. L’implementazione ha fatto emergere problemi che il solo progetto non aveva rivelato.
- Preparare il progetto e i test Sono stati preparati il progetto Rust, gli strumenti di test e i primi programmi di esempio. La specifica è stata fissata come riferimento. Il suo limite iniziale era ancora una stima e il successivo conteggio dei token lo ha modificato.
- Sottoporre il piano a revisione Cinque revisori hanno esaminato criticamente il piano prima dell’implementazione. Hanno valutato progettazione del linguaggio, compilatore, semplicità, uso da parte dei modelli e didattica.
- L’idea iniziale Il progetto è iniziato con un documento e una discussione sul linguaggio. L’obiettivo era un linguaggio compilato che usasse librerie C e fosse progettato per i modelli che generano codice.
Le revisioni, le misure e i diari completi sono nel repository, aperto in lettura. Puoi seguire gli aggiornamenti tramite il feed per sapere quando arriva la prossima tappa.