Gli autori di compilatori devono realmente "comprendere" il codice macchina? [chiuso]


10

Potrebbe essere una specie di domanda strana.

Un tizio che scrive un compilatore C ++ (o qualunque linguaggio non VM): deve essere in grado di leggere / scrivere un linguaggio macchina grezzo? Come funziona?

EDIT: mi riferisco in particolare ai compilatori che vengono compilati in base al codice macchina, non ad altri linguaggi di programmazione.



1
No. Non hai nemmeno bisogno di saperlo, puoi semplicemente copiare alla cieca, insensatamente le tue specifiche ISA target: dl.acm.org/citation.cfm?id=1706346
SK-logic

1
Coffescript si compila in javascript.
Kartik

@Kartik Il compilatore CoffeeScript che si compila in Javascript, include anche un compilatore Javascript che si compila in qualunque Javascript venga compilato? O si compila solo in codice sorgente Javascript e nient'altro?
Aviv Cohn,

Il compilatore di coffeescript converte semplicemente cofeescript in javascript. Javascript non è compilato, è gestito dal browser. Volevo dire che puoi scrivere un compilatore che compila una lingua in un'altra, per questo non devi conoscere il linguaggio macchina. Un altro esempio è il compilatore "SPL" che compila i giochi di Shakespeare in C ++. shakespearelang.sourceforge.net
Kartik

Risposte:


15

No, per niente. È invece perfettamente possibile (e spesso anche preferibile) per il compilatore emettere il codice assembly. L'assemblatore si occupa quindi di creare il codice macchina effettivo.

A proposito, la distinzione tra implementazione non VM e implementazione VM non è utile.

  • Per i principianti, l'utilizzo di una macchina virtuale o la precompilazione per il codice macchina sono solo modi diversi di implementare una lingua; nella maggior parte dei casi una lingua può essere implementata usando entrambe le strategie. In realtà ho dovuto usare un interprete C ++ una volta.

  • Inoltre, molte macchine virtuali come la JVM hanno entrambe un codice binario per la macchina e alcuni assemblatori, proprio come un'architettura normale.

LLVM (che viene utilizzato dai compilatori Clang) merita una menzione speciale qui: definisce una VM per la quale le istruzioni possono essere rappresentate come codice byte, assembly testuale o una struttura di dati che semplifica l'emissione da un compilatore. Quindi, sebbene sarebbe utile per il debug (e per capire cosa stai facendo), non dovresti nemmeno conoscere il linguaggio assembly, solo l'API LLVM.

La cosa bella di LLVM è che la sua VM è solo un'astrazione, e che il codice byte non viene generalmente interpretato, ma invece JITted in modo trasparente. Quindi è del tutto possibile scrivere una lingua che è stata compilata in modo efficace, senza mai dover conoscere il set di istruzioni della CPU.


E un'altra bella proprietà di LLVM è che non è necessario comprendere a fondo l'ISA target per implementare un backend efficiente. È piuttosto dichiarativo, quindi si può quasi copiare e incollare una specifica ISA in file .td senza nemmeno provare a capirlo.
SK-logic,

Grazie per aver risposto. Domanda: capisco dalla tua risposta e dalle risposte altrui che il compilatore-scrittore non deve capire il codice macchina, può usare un altro strumento che fa il vero codice di conversione in macchina per lui. Tuttavia, il ragazzo che ha scritto che strumento ha bisogno di comprendere il linguaggio macchina, giusto? Il tizio che ha scritto il software che esegue la conversione effettiva da una lingua a un codice macchina deve capire il linguaggio macchina giusto?
Aviv Cohn,

5
@Prog si. Se crei uno strato di astrazione, devi solo capire il livello sotto di te. Sebbene sia utile avere una conoscenza di base di tutti i livelli, questo non è davvero necessario. Non è necessario comprendere la fisica quantistica per utilizzare un transistor. Non è necessario comprendere la progettazione del chip per utilizzare una CPU. Non è necessario conoscere il codice macchina per scrivere assembly. Non è necessario conoscere il set di istruzioni della piattaforma quando si utilizza una VM, ecc. Ma qualcuno ha dovuto costruire quei livelli di astrazione al di sotto del proprio.
amon

1
@ SK-logic Non è vero (almeno se vuoi un buon codice) da quello che ho sentito. Le persone che hanno implementato il back-end Aarch64 per llvm hanno avuto parecchie sfide (trasferimenti per uno, schemi di caricamento dei carichi orribili, ..). E questo sta ignorando il più grande elefante nella stanza: il modello di memoria dell'ISA e il modello di memoria del linguaggio che ti interessa. Puoi lavorare su un compilatore, ma non puoi lavorare su un backend senza capire l'architettura ...
Voo,

1
Vorrei aggiungere che non c'è davvero alcuna differenza sostanziale tra assembly e codice macchina, concettualmente. Non avrai davvero molti benefici, una volta che sai come usare una particolare istruzione, qual è il codice operativo di quell'istruzione. Se sai come usare MOV, non importa se non sai che si tratta dell'istruzione 27, che penso sia simile a ciò che @ SK-logic sta descrivendo.
whatsisname

9

No. Il punto chiave della tua domanda è che la compilazione è un termine estremamente ampio. La compilazione può avvenire da qualsiasi lingua a qualsiasi lingua. E il codice assembly / machine è solo una delle molte lingue per la destinazione della compilazione. Ad esempio, i linguaggi Java e .NET come C #, F # e VB.NET vengono tutti compilati in un tipo di codice intermedio anziché in codice specifico della macchina. Non importa se viene eseguito su VM, la lingua è ancora compilata. C'è anche la possibilità di compilare in qualche altra lingua, come C. C è in realtà un obiettivo di compilazione piuttosto popolare e molti strumenti lo fanno. E infine, potresti usare alcuni strumenti o librerie per fare il duro lavoro di produzione del codice macchina per te. esiste ad esempio LLVM che può ridurre gli sforzi necessari per creare un compilatore autonomo.

Inoltre, la modifica non ha alcun senso. È come chiedere "Ogni ingegnere deve capire come funziona il motore? E sto chiedendo degli ingegneri che lavorano sui motori". Se stai lavorando su un programma o una libreria che emette un codice macchina, devi capirlo. Il punto è che non devi fare una cosa simile quando scrivi un compilatore. Molte persone l'hanno fatto prima di te, quindi devi avere serie ragioni per farlo di nuovo.


E la persona che scrive lo strumento o la libreria che esegue la conversione effettiva in linguaggio macchina, deve comprendere completamente il linguaggio macchina, giusto?
Aviv Cohn,

3
@Prog Hai bisogno di comprendere appieno un linguaggio di programmazione per programmarlo? No, ma potresti scrivere un codice non ottimale e non puoi fare certe cose che altri potrebbero essere in grado di fare. Hai bisogno di comprendere completamente il linguaggio macchina se scrivi un compilatore che si traduce in quello. No, ma il tuo compilatore non sarà ottimale e non sarà in grado di fare determinate cose.
Sumurai 8

@ Sumurai8: anche se in una certa misura è possibile "capire" il linguaggio macchina in modo parziale per poter scrivere un emettitore di codice macchina che capisca tutto meglio di te. Ad esempio, se si scrive un buon framework, è possibile configurare la definizione di ciascun opcode insieme ai suoi costi e considerazioni sulla pipeline, e quindi il proprio framework può scrivere un codice macchina ottimizzato anche se non si ha alcuna esperienza nell'ottimizzare quella particolare macchina. Essere in grado di programmare quel codice macchina con competenza da soli probabilmente non fa male.
Steve Jessop,

@SteveJessop Se capisci ogni codice operativo fino a un punto in cui puoi imparare una macchina come concatenare quel codice operativo insieme ad altri codici operativi per esprimere un concetto di livello superiore, capisci completamente il linguaggio della macchina. Sei quindi troppo pigro per trovare la soluzione ottimale per ogni problema là fuori ;-)
Sumurai8

@ Sumurai8: hmm, ma almeno in linea di principio potrei "capire" brevemente ogni codice operativo per i cinque minuti che mi occorrono per configurarlo, e dopo averlo "dimenticato" dopo averlo capito. Questo probabilmente non è ciò che l'interrogante intende per "essere in grado di leggere / scrivere un linguaggio macchina grezzo". Ovviamente sto assumendo un buon quadro, che è abbastanza configurabile per definire e usare tutte le informazioni utili su ogni codice operativo del set di istruzioni. LLVM punta in qualche modo a questo, ma secondo "Voo" (in un commento qui sotto) non l'ha colpito.
Steve Jessop,

3

Classicamente un compilatore ha tre parti: analisi lessicale, analisi e generazione di codice. L'analisi lessicale suddivide il testo del programma in parole chiave, nomi e valori della lingua. L'analisi calcola come i token che derivano dall'analisi lessicale sono combinati in istruzioni sintatticamente corrette per la lingua. La generazione del codice prende le strutture di dati prodotte dal parser e le traduce in codice macchina o in qualche altra rappresentazione. Oggi l'analisi lessicale e l'analisi possono essere combinate in un unico passaggio.

Chiaramente la persona che scrive il generatore di codice deve comprendere il codice macchina di destinazione a un livello molto profondo, inclusi set di istruzioni, pipeline del processore e comportamento della cache. Altrimenti i programmi prodotti dal compilatore sarebbero lenti e inefficienti. Potrebbero benissimo essere in grado di leggere e scrivere codice macchina come rappresentato da numeri ottali o esadecimali, ma generalmente scriveranno funzioni per generare il codice macchina, facendo riferimento internamente alle tabelle delle istruzioni della macchina. Teoricamente le persone che scrivono il lexer e il parser potrebbero non sapere nulla della generazione del codice macchina. In effetti, alcuni compilatori moderni ti consentono di collegare le tue routine di generazione del codice che potrebbero emettere codice macchina per alcune CPU di cui gli autori lexer e parser non hanno mai sentito parlare.

Tuttavia, in pratica gli autori di compilatori in ogni fase conoscono molto le diverse architetture di processori e ciò li aiuta a progettare le strutture di dati necessarie per la fase di generazione del codice.


2

Molto tempo fa ho scritto un compilatore che è stato convertito tra due diversi script di shell. Non si avvicinava affatto al codice macchina.

Una scrittura del compilatore deve comprendere il loro output , ma spesso non si tratta di codice macchina.

La maggior parte dei programmatori non lo farà mai scriverà un compilatore che emette il codice macchina o il codice assembly, ma i compilatori personalizzati possono essere molto utili su molti progetti per produrre altri output.

YACC è uno di questi compilatori che non genera codice macchina ...


0

Non è necessario iniziare con una conoscenza dettagliata della semantica delle lingue di input e output, ma è meglio finire con una conoscenza squisitamente dettagliata di entrambi, altrimenti il ​​compilatore sarà insolitamente difettoso. Quindi, se il tuo input è C ++ e il tuo output è un linguaggio macchina specifico, alla fine dovrai conoscere la semantica di entrambi.

Ecco alcune delle sottigliezze nella compilazione di C ++ in codice macchina: (appena fuori dalla mia testa, sono sicuro che ce ne sono altre che sto dimenticando.)

  1. Che taglia sarà int? La scelta "corretta" qui è un'arte, basata sia sulla dimensione naturale del puntatore della macchina, sulle prestazioni dell'ALU per varie dimensioni di operazioni aritmetiche, sia sulle scelte fatte dai compilatori esistenti per la macchina. La macchina ha anche un'aritmetica a 64 bit? In caso contrario, l'aggiunta di numeri interi a 32 bit dovrebbe tradursi in un'istruzione, mentre l'aggiunta di numeri interi a 64 bit dovrebbe tradursi in una chiamata di funzione per eseguire l'aggiunta a 64 bit. La macchina ha operazioni di aggiunta a 8 e 16 bit o è necessario simulare quelle con operazioni e mascheramento a 32 bit (ad esempio DEC Alpha 21064)?

  2. Qual è la convenzione di chiamata utilizzata da altri compilatori, librerie e lingue sulla macchina? I parametri vengono inseriti nello stack da destra a sinistra o da sinistra a destra? Alcuni parametri vanno nei registri mentre altri vanno nello stack? Ints e float si trovano in spazi di registro diversi? I parametri assegnati al registro devono essere trattati in modo particolare sulle chiamate varargs? Quali registri vengono salvati dal chiamante e quali vengono salvati? Puoi eseguire ottimizzazioni foglia-chiamata?

  3. Cosa fanno ciascuna delle istruzioni di cambio della macchina? Se si chiede di spostare un intero di 64 bit di 65 bit, qual è il risultato? (Su molte macchine il risultato è uguale allo spostamento di 1 bit, su altre il risultato è "0".)

  4. Quali sono la semantica di coerenza della memoria della macchina? C ++ 11 ha una semantica della memoria molto ben definita che pone restrizioni su alcune ottimizzazioni in alcuni casi, ma consente ottimizzazioni in altri casi. Se stai compilando un linguaggio che non ha una semantica della memoria ben definita (come ogni versione di C / C ++ prima di C ++ 11 e molti altri linguaggi imperativi), dovrai inventare la semantica della memoria mentre procedi, e di solito vorrai inventare la semantica della memoria che si adatta meglio alla semantica della tua macchina.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.