„Assembler in C schreiben“ Warum einen Maschinencode-Übersetzer für eine niedrigere Sprache in einer höheren Sprache schreiben?

13

Mein Mikroprozessor Klassenlehrer gab uns einen Auftrag und sagte:

"Schreiben Sie einen Assembler in C." - Mein geliebter Professor

Es kam mir ein bisschen unlogisch vor.

Wenn ich mich nicht irre, ist die Assemblersprache der erste Schritt vom Maschinencode zum Erlernen übergeordneter Sprachen. Ich meine, C ist eine höhere Sprache als Assembly. Was bringt es also, einen Assembler in C zu schreiben? Was machten sie in der Vergangenheit, während die C-Sprache fehlte? Haben sie Assembler in Maschinencode geschrieben?

Es macht für mich keinen Sinn, einen Maschinencode-Übersetzer für eine niedrigere Sprache in einer höheren Sprache zu schreiben.

Angenommen, wir haben eine brandneue Mikroprozessorarchitektur erstellt, für die es nicht einmal einen C-Compiler gibt. Wird unser in C geschriebener Assembler die neue Architektur simulieren können? Ich meine, wird es nutzlos sein oder nicht?

Übrigens ist mir bekannt, dass GNU Assembler und Netwide Assembler in C geschrieben wurden. Ich frage mich auch, warum sie in C geschrieben wurden.

Zuletzt ist dies der Beispielquellcode für einen einfachen Assembler, den uns unser Professor gegeben hat:

// to compile, gcc assembler.c -o assembler
// No error check is provided.
// Variable names cannot start with 0-9.
// hexadecimals are twos complement.
// first address of the code section is zero, data section follows the code section.
//fout tables are formed: jump table, ldi table, label table and variable table.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//Converts a hexadecimal string to integer.
int hex2int( char* hex)  
{
    int result=0;

    while ((*hex)!='\0')
    {
        if (('0'<=(*hex))&&((*hex)<='9'))
            result = result*16 + (*hex) -'0';
        else if (('a'<=(*hex))&&((*hex)<='f'))
            result = result*16 + (*hex) -'a'+10;
        else if (('A'<=(*hex))&&((*hex)<='F'))
            result = result*16 + (*hex) -'A'+10; 
        hex++;
    }
    return(result);
}


main()
{   
    FILE *fp;
        char line[100];
        char *token = NULL;
    char *op1, *op2, *op3, *label;
    char ch;
    int  chch;

    int program[1000];
    int counter=0;  //holds the address of the machine code instruction




// A label is a symbol which mark a location in a program. In the example 
// program above, the string "lpp", "loop" and "lp1" are labels.
    struct label  
    {
        int location;
        char *label;
    };
    struct label labeltable[50]; //there can be 50 labels at most in our programs
    int nooflabels = 0; //number of labels encountered during assembly.




// Jump instructions cannot be assembled readily because we may not know the value of 
// the label when we encountered a jump instruction. This happens if the label used by
// that jump instruction appear below that jump instruction. This is the situation 
// with the label "loop" in the example program above. Hence, the location of jump 
// instructions must be stored.
    struct jumpinstruction   
    {
        int location;
        char *label;
    };
    struct jumpinstruction jumptable[100]; //There can be at most 100 jumps
    int noofjumps=0;  //number of jumps encountered during assembly.    




// The list of variables in .data section and their locations.
    struct variable
    {
        int location;
        char *name;
    };
    struct variable variabletable[50]; //There can be 50 varables at most.
    int noofvariables = 0;




//Variables and labels are used by ldi instructions.
//The memory for the variables are traditionally allocated at the end of the code section.
//Hence their addresses are not known when we assemble a ldi instruction. Also, the value of 
//a label may not be known when we encounter a ldi instruction which uses that label.
//Hence, the location of the ldi instructions must be kept, and these instructions must be 
//modified when we discover the address of the label or variable that it uses.
    struct ldiinstruction   
    {
        int location;
        char *name;
    };
    struct ldiinstruction lditable[100];
    int noofldis=0;




    fp = fopen("name_of_program","r");

    if (fp != NULL)
    {
        while(fgets(line,sizeof line,fp)!= NULL)  //skip till .code section
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".code")==0 )
                break;
        } 
        while(fgets(line,sizeof line,fp)!= NULL)
        {
            token=strtok(line,"\n\t\r ");  //get the instruction mnemonic or label

//========================================   FIRST PASS  ======================================================
            while (token)
            {
                if (strcmp(token,"ldi")==0)        //---------------LDI INSTRUCTION--------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");                                //get the 1st operand of ldi, which is the register that ldi loads
                    op2 = strtok(NULL,"\n\t\r ");                                //get the 2nd operand of ldi, which is the data that is to be loaded
                    program[counter]=0x1000+hex2int(op1);                        //generate the first 16-bit of the ldi instruction
                    counter++;                                                   //move to the second 16-bit of the ldi instruction
                    if ((op2[0]=='0')&&(op2[1]=='x'))                            //if the 2nd operand is twos complement hexadecimal
                        program[counter]=hex2int(op2+2)&0xffff;              //convert it to integer and form the second 16-bit 
                    else if ((  (op2[0])=='-') || ((op2[0]>='0')&&(op2[0]<='9')))       //if the 2nd operand is decimal 
                        program[counter]=atoi(op2)&0xffff;                         //convert it to integer and form the second 16-bit 
                    else                                                           //if the second operand is not decimal or hexadecimal, it is a laber or a variable.
                    {                                                               //in this case, the 2nd 16-bits of the ldi instruction cannot be generated.
                        lditable[noofldis].location = counter;                 //record the location of this 2nd 16-bit  
                        op1=(char*)malloc(sizeof(op2));                         //and the name of the label/variable that it must contain
                        strcpy(op1,op2);                                        //in the lditable array.
                        lditable[noofldis].name = op1;
                        noofldis++;                                             
                    }       
                    counter++;                                                     //skip to the next memory location 
                }                                       

                else if (strcmp(token,"ld")==0)      //------------LD INSTRUCTION---------------------         
                {
                    op1 = strtok(NULL,"\n\t\r ");                //get the 1st operand of ld, which is the destination register
                    op2 = strtok(NULL,"\n\t\r ");                //get the 2nd operand of ld, which is the source register
                    ch = (op1[0]-48)| ((op2[0]-48) << 3);        //form bits 11-0 of machine code. 48 is ASCII value of '0'
                    program[counter]=0x2000+((ch)&0x00ff);       //form the instruction and write it to memory
                    counter++;                                   //skip to the next empty location in memory
                }
                else if (strcmp(token,"st")==0) //-------------ST INSTRUCTION--------------------
                {
                    //to be added
                }
                else if (strcmp(token,"jz")==0) //------------- CONDITIONAL JUMP ------------------
                {
                    //to be added
                }
                else if (strcmp(token,"jmp")==0)  //-------------- JUMP -----------------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");           //read the label
                    jumptable[noofjumps].location = counter;    //write the jz instruction's location into the jumptable 
                    op2=(char*)malloc(sizeof(op1));         //allocate space for the label                  
                    strcpy(op2,op1);                //copy the label into the allocated space
                    jumptable[noofjumps].label=op2;         //point to the label from the jumptable
                    noofjumps++;                    //skip to the next empty location in jumptable
                    program[counter]=0x5000;            //write the incomplete instruction (just opcode) to memory
                    counter++;                  //skip to the next empty location in memory.
                }               
                else if (strcmp(token,"add")==0) //----------------- ADD -------------------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");    
                    op2 = strtok(NULL,"\n\t\r ");
                    op3 = strtok(NULL,"\n\t\r ");
                    chch = (op1[0]-48)| ((op2[0]-48)<<3)|((op3[0]-48)<<6);  
                    program[counter]=0x7000+((chch)&0x00ff); 
                    counter++; 
                }
                else if (strcmp(token,"sub")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"and")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"or")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"xor")==0)
                {
                    //to be added
                }                       
                else if (strcmp(token,"not")==0)
                {
                    op1 = strtok(NULL,"\n\t\r ");
                    op2 = strtok(NULL,"\n\t\r ");
                    ch = (op1[0]-48)| ((op2[0]-48)<<3);
                    program[counter]=0x7500+((ch)&0x00ff);  
                    counter++;
                }
                else if (strcmp(token,"mov")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"inc")==0)
                {
                    op1 = strtok(NULL,"\n\t\r ");
                    ch = (op1[0]-48)| ((op1[0]-48)<<3);
                    program[counter]=0x7700+((ch)&0x00ff);  
                    counter++;
                }
                else if (strcmp(token,"dec")==0)
                {
                                    //to be added
                }
                else //------WHAT IS ENCOUNTERED IS NOT AN INSTRUCTION BUT A LABEL. UPDATE THE LABEL TABLE--------
                {
                    labeltable[nooflabels].location = counter;  //buraya bir counter koy. error check
                    op1=(char*)malloc(sizeof(token));
                    strcpy(op1,token);
                    labeltable[nooflabels].label=op1;
                    nooflabels++;
                } 
                token = strtok(NULL,",\n\t\r ");  
            }
        }


//================================= SECOND PASS ==============================

                //supply the address fields of the jump and jz instructions from the 
        int i,j;         
        for (i=0; i<noofjumps;i++)                                                                   //for all jump/jz instructions
        {
            j=0;
            while ( strcmp(jumptable[i].label , labeltable[j].label) != 0 )             //if the label for this jump/jz does not match with the 
                j++;                                                                // jth label in the labeltable, check the next label..
            program[jumptable[i].location] +=(labeltable[j].location-jumptable[i].location-1)&0x0fff;       //copy the jump address into memory.
        }                                                     




                // search for the start of the .data segment
        rewind(fp);  
        while(fgets(line,sizeof line,fp)!= NULL)  //skip till .data, if no .data, also ok.
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".data")==0 )
                break;

        }


                // process the .data segment and generate the variabletable[] array.
        int dataarea=0;
        while(fgets(line,sizeof line,fp)!= NULL)
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".code")==0 )  //go till the .code segment
                break;
            else if (token[strlen(token)-1]==':')
            {               
                token[strlen(token)-1]='\0';  //will not cause memory leak, as we do not do malloc
                variabletable[noofvariables].location=counter+dataarea;
                op1=(char*)malloc(sizeof(token));
                strcpy(op1,token);
                variabletable[noofvariables].name=op1;
                token = strtok(NULL,",\n\t\r ");
                if (token==NULL)
                    program[counter+dataarea]=0;
                else if (strcmp(token, ".space")==0)
                {
                    token=strtok(NULL,"\n\t\r ");
                    dataarea+=atoi(token);
                }
                else if((token[0]=='0')&&(token[1]=='x')) 
                    program[counter+dataarea]=hex2int(token+2)&0xffff; 
                else if ((  (token[0])=='-') || ('0'<=(token[0])&&(token[0]<='9'))  )
                    program[counter+dataarea]=atoi(token)&0xffff;  
                noofvariables++;
                dataarea++;
            }
        }






// supply the address fields for the ldi instructions from the variable table
        for( i=0; i<noofldis;i++)
        {
            j=0;
            while ((j<noofvariables)&&( strcmp( lditable[i].name , variabletable[j].name)!=0 ))
                j++;
            if (j<noofvariables)
                program[lditable[i].location] = variabletable[j].location;              
        } 

// supply the address fields for the ldi instructions from the label table
        for( i=0; i<noofldis;i++)
        {
            j=0;
            while ((j<nooflabels)&&( strcmp( lditable[i].name , labeltable[j].label)!=0 ))
                j++;
            if (j<nooflabels){
                program[lditable[i].location] = (labeltable[j].location)&0x0fff;
                printf("%d %d %d\n", i, j, (labeltable[j].location));   
            }           
        } 

//display the resulting tables
        printf("LABEL TABLE\n");
        for (i=0;i<nooflabels;i++)
            printf("%d %s\n", labeltable[i].location, labeltable[i].label); 
        printf("\n");
        printf("JUMP TABLE\n");
        for (i=0;i<noofjumps;i++)
            printf("%d %s\n", jumptable[i].location, jumptable[i].label);   
        printf("\n");
        printf("VARIABLE TABLE\n");
        for (i=0;i<noofvariables;i++)
            printf("%d %s\n", variabletable[i].location, variabletable[i].name);    
        printf("\n");
        printf("LDI INSTRUCTIONS\n");
        for (i=0;i<noofldis;i++)
            printf("%d %s\n", lditable[i].location, lditable[i].name);  
        printf("\n");
        fclose(fp);
        fp = fopen("RAM","w");
        fprintf(fp,"v2.0 raw\n");
        for (i=0;i<counter+dataarea;i++)
            fprintf(fp,"%04x\n",program[i]);
    }   
}
Mertyildiran
quelle
2
Für sich existiert kein Gerät. Cross-Toolchains sind besonders bei kleinen Architekturen sehr verbreitet.
Lars Viklund
3
Ein "Cross" -Compiler / Assembler wird auf einem anderen System als dem Ziel ausgeführt und erzeugt Artefakte, die für die Verwendung auf dem Zielsystem geeignet sind. In der Antike gab es nicht unbedingt einen Datenaustausch zwischen Systemen, sondern man musste ein System von Grund auf neu booten. Nahezu die gesamte moderne Entwicklung für Architekturen erfolgt auf etablierten Systemen, die alles übergreifend kompilieren.
Lars Viklund
19
Möchten Sie den Assembler in Maschinencode anstelle von C schreiben? Ihr Professor ist nett zu Ihnen.
Winston Ewert
2
Warum würden Sie nicht versuchen, Ihren gesamten Code in der bestmöglichen Programmierumgebung / -sprache zu schreiben? Ein Assembler ist keine Ausnahme.
Erik Eidt
1
Es gibt keine feste "Reise" in eine bestimmte Richtung.
Whatsisname

Antworten:

17

Die Leute haben Assembler in Maschinencode geschrieben. Sie haben dann auch in Assemblersprache geschrieben - oft eine Teilmenge der Sprache, die sie selbst übersetzen. Sie beginnen also mit einer einfachen "Bootstrap" -Version des Assemblers und fügen ihr dann Funktionen hinzu, wenn sie sie für den Assembler selbst benötigen.

Nichts davon ist jedoch eine besondere Notwendigkeit. Am Ende ist ein Assembler ein (normalerweise recht einfaches) Übersetzungsprogramm. Es nimmt eine Datei in einem (Text-) Format auf und schreibt eine Datei in einem anderen (normalerweise einem Objektdateiformat).

Die Tatsache, dass der eingegebene Text Maschinenanweisungen in einem Textformat darstellt und das Ergebnis dieselben Anweisungen im Binärformat darstellt, hat keinen großen Einfluss auf die Sprache, in der der Assembler implementiert wird - in der Tat sogar in höheren Sprachen als C, z Da SNOBOL und Python recht gut funktionieren können, habe ich (ziemlich) kürzlich an einem Assembler gearbeitet, der in Python geschrieben wurde, und er hat für diesen Job ziemlich gut funktioniert.

So weit wie Sie die Dinge anfangs booten: Normalerweise auf einem anderen Computer mit anständigen Entwicklungstools und so weiter. Wenn Sie neue Hardware entwickeln, schreiben Sie normalerweise ohnehin einen Simulator (oder zumindest einen Emulator) für den neuen Computer. Zunächst müssen Sie den Code also auf einem bestimmten Hostsystem erstellen und ausführen.

Jerry Sarg
quelle
3
"Selbst höhere Sprachen als C wie SNOBOL und Python können ganz gut funktionieren" - das ist ein sehr guter Punkt. Für NASM haben wir nie wirklich etwas Höheres als C in Betracht gezogen, aber 1995 war die Leistung viel wichtiger als heute und die Hochsprachen waren viel weniger fortgeschritten als heute. In diesen Tagen lohnt es sich sicherlich, über Alternativen nachzudenken.
Jules
1
Ich habe den Namen SNOBOL seit den 1980er Jahren nicht mehr gehört.
Pacmaninbw
Ich habe einmal einen Compiler in Haskell geschrieben. Die verzögerte Auswertung und Verkettung von Funktionen machte es einfach, einen Gucklochoptimierer für den generierten Maschinencode zu schreiben.
Thorbjørn Ravn Andersen
10

Sie sehen Verbindungen, die nicht existieren.

"Assembler schreiben" ist wie jede andere Programmieraufgabe eine Programmieraufgabe. Sie verwenden die Tools, um die Aufgabe zu erledigen, die für diese Aufgabe am besten geeignet ist. Es ist nichts Besonderes, einen Assembler zu schreiben. Es gibt überhaupt keinen Grund, es nicht in einer höheren Sprache zu schreiben. C ist eigentlich auf einem recht niedrigen Niveau, und ich würde wahrscheinlich C ++ oder eine andere höhere Sprache bevorzugen.

Die Assemblersprache ist für eine solche Aufgabe eigentlich völlig ungeeignet. Die Fälle, in denen Sie vernünftigerweise Assemblersprache verwenden würden, sind sehr, sehr selten. Nur wenn Sie Dinge tun müssen, die nicht in einer höheren Sprache ausgedrückt werden können.

gnasher729
quelle
1
Die anderen Antworten sind sehr gut, aber ich finde, dass diese am einfachsten ist, besonders mit den ersten beiden Sätzen. Ich habe mir das gleiche gesagt, als ich die Frage gelesen habe.
MetalMikester
Das manuelle Schreiben der Assemblersprache wird heutzutage nur für hardwarespezifische Hacks benötigt. Das Einrichten des geschützten Modus auf einigen CPUs erfordert beispielsweise eine bestimmte Befehlssequenz, und eine logisch äquivalente Sequenz ist nicht gut genug. Nahezu alle normalen Programme erfordern keine spezifische Befehlssequenz für die Aufgabe, die sie ausführen müssen, und daher gibt es keinen Grund, eine spezifische Sequenz zu fordern, sondern nur einen logisch äquivalenten Befehlssatz. Die Optimierung von Compilern verbessert auf die gleiche Weise die Ausführungsleistung (Anzahl der Befehle, Uhrzeit der Wanduhr, Größe des Code-Caches).
Mikko Rantalainen
9

Was machten sie in der Vergangenheit, während die C-Sprache fehlte? Haben sie Assembler in Maschinencode geschrieben?

Assembly ist im Wesentlichen eine Kurzbezeichnung für Maschinencode. Jeder Opcode in der Maschinensprache erhält eine Assembly-Mnemonik, dh in x86 ist NOP 0x90. Dies macht Assembler ziemlich einfach (nb die meisten Assembler haben zwei Durchgänge, einen zum Übersetzen und einen zweiten zum Erzeugen / Auflösen von Adressen / Referenzen.) Der erste Assembler wurde von Hand (wahrscheinlich auf Papier) in Maschinencode geschrieben und übersetzt. Eine bessere Version wird mit dem handmontierten Assembler geschrieben und zusammengestellt. Auf diese Weise werden neue Funktionen hinzugefügt. Auf diese Weise können Compiler für neue Sprachen erstellt werden. In der Vergangenheit war es üblich, dass Compiler Assemblys ausgeben und einen Assembler für ihr Back-End verwenden!

Es macht für mich keinen Sinn, einen Maschinencode-Übersetzer für eine niedrigere Sprache in einer höheren Sprache zu schreiben. ... [bestehende Assembler] wurden in C geschrieben. Ich frage mich auch, warum sie in C geschrieben sind.

  • Es ist im Allgemeinen einfacher, eine kompliziertere Software in einer höheren Sprache zu schreiben.
  • Im Allgemeinen ist mehr Code und mehr geistige Anstrengung erforderlich, um zu verfolgen, was Sie in einer niedrigeren Sprache als in einer höheren Sprache tun.
    • Eine einzelne Zeile von C könnte in viele Anweisungen übersetzt werden, z. Eine einfache Zuweisung in C ++ (oder C) erzeugt normalerweise mindestens 3 Assembler-Anweisungen (Laden, Ändern, Speichern;). Es können zwanzig oder mehr Anweisungen (möglicherweise Hunderte) erforderlich sein, um das zu tun, was mit einer einzelnen Zeile auf einer höheren Ebene erreicht werden kann sprache (wie c ++ oder c.) Man möchte im Allgemeinen seine Zeit mit der Lösung des Problems verbringen und nicht damit, herauszufinden, wie die Lösung in Maschinencode implementiert werden kann.

Während Selbsthosting ein häufiger Meilenstein / wünschenswertes Merkmal für eine Programmiersprache ist, ist die Assemblierungsstufe so niedrig, dass die meisten Programmierer es vorziehen würden, auf einer höheren Stufe zu arbeiten. Dh niemand möchte einen Assembler in Assembler schreiben (oder irgendetwas anderes wirklich)

Angenommen, wir haben eine brandneue Mikroprozessorarchitektur erstellt, für die es nicht einmal einen C-Compiler gibt.

Beim Bootstrapping wird eine Toolkette für eine neue Architektur erstellt.

Der grundlegende Prozess ist:

  • Schreiben Sie ein neues Backend, das versteht, wie Code für Ihre neue CPU (oder MCU) generiert wird.
  • Kompilieren und testen Sie Ihr Backend
  • Kompilieren Sie Ihren gewünschten Compiler (und Betriebssysteme usw.) über Ihr neues Backend
  • Übertragen Sie diese Binärdateien auf das neue System

Um dies zu tun, müssen Sie nicht einmal in Assembly (neu oder alt) schreiben. Sie sollten die beste Sprache für das Schreiben Ihres Assemblers / Back-End / Code-Generators auswählen.

Wird unser in C geschriebener Assembler die neue Architektur simulieren können?

Assembler simulieren nicht!

Wenn eine neue CPU mit einer neuen (oder vorhandenen) Maschinensprache entwickelt wurde, ist normalerweise ein Simulator zum Testen erforderlich. Führen Sie also zufällige Anweisungen und Daten durch den Simulator und vergleichen Sie die Ausgabe mit denselben Anweisungen und Daten auf Ihrer Prototyp-CPU. Dann finden Sie Fehler, beheben Sie Fehler, wiederholen Sie.

esoterik
quelle
3

Zu den Gründen, einen Assembler in C (oder einer anderen höheren Programmiersprache) zu schreiben, gehören alle Gründe, die Sie möglicherweise angeben, um das Schreiben eines anderen Programms in dieser höheren Programmiersprache zu rechtfertigen. Das Wichtigste in diesem Fall ist wahrscheinlich die Portabilität und Benutzerfreundlichkeit.

Portabilität: Wenn Sie Ihren Assembler in einer Muttersprache schreiben, haben Sie einen Assembler auf dieser Plattform. Wenn Sie es in C schreiben, haben Sie einen Assembler auf jeder Plattform mit einem C-Compiler. Auf diese Weise können Sie beispielsweise Code für Ihre eingebettete Plattform auf Ihrer Workstation kompilieren und die Binärdatei verschieben, anstatt alles direkt auf dem Zielgerät ausführen zu müssen.

Benutzerfreundlichkeit: Für die meisten Menschen ist das Lesen, Überlegen und Ändern von Programmen weitaus natürlicher, wenn das Programm in einer höheren Programmiersprache vorliegt als in Assembler- oder (schlechteren) Raw-Maschinencode. Daher ist es einfacher, den Assembler in einer höheren Sprache zu entwickeln und zu verwalten, da Sie in Bezug auf die Abstraktionen denken können, die Ihnen von den höheren Sprachen geboten werden, anstatt über die Minutien nachdenken zu müssen, für die Sie in niedrigeren Sprachen verantwortlich sind.

Benutzer nicht gefunden
quelle
3

Nur auf diesen Teil der Frage eingehen:

"Übrigens ist mir bekannt, dass GNU Assembler und Netwide Assembler in C geschrieben wurden. Ich frage mich auch, warum sie in C geschrieben wurden."

Als Teil des Teams, das ursprünglich den Netwide Assembler geschrieben hat, erschien uns die Entscheidung zu diesem Zeitpunkt so offensichtlich, dass wir grundsätzlich keine anderen Optionen in Betracht zogen die folgenden Gründe:

  • Es wäre schwieriger und zeitaufwändiger gewesen, es in einer niedrigeren Sprache zu schreiben.
  • Das Schreiben in einer höheren Sprache mag schneller gewesen sein, aber es gab Leistungsaspekte (ein Assembler, der als Back-End für einen Compiler verwendet wird, muss besonders schnell sein, um zu verhindern, dass der Compiler so schnell wie möglich langsamer wird) Englisch: emagazine.credit-suisse.com/app/art ... = 157 & lang = en Am Ende werden sehr große Mengen an Code verarbeitet, und dies war ein Anwendungsfall, den wir ausdrücklich zulassen wollten. Ich glaube nicht, dass die primären Autoren gemeinsame Sprachen auf höherer Ebene hatten (dies war, bevor Java populär wurde, also die Welt von Solche Sprachen waren damals eher fragmentiert. Wir haben Perl für einige Metaprogrammierungsaufgaben verwendet (Generieren von Anweisungstabellen in einem nützlichen Format für das Code-Generator-Back-End), aber es wäre nicht wirklich für das gesamte Programm geeignet gewesen.
  • Wir wollten Betriebssystem-Portabilität
  • Wir wollten Hardware-Plattform-Portabilität (für die Herstellung von Cross-Compilern)

Dies machte die Entscheidung ziemlich einfach: ANSI-konformes C (auch bekannt als C89) war zu diesem Zeitpunkt die einzige Sprache, die all diese Punkte wirklich traf. Hätte es damals ein standardisiertes C ++ gegeben, hätten wir das vielleicht in Betracht gezogen, aber die C ++ - Unterstützung zwischen verschiedenen Systemen war damals ziemlich lückenhaft, so dass das Schreiben von portablem C ++ ein Albtraum war.

Jules
quelle
1

Eine Sache hat absolut nichts mit der anderen zu tun. Sollen Webbrowser ausschließlich mit HTML, PHP oder einer anderen Sprache für Webinhalte geschrieben werden? Nein, warum sollten sie? Können Autos nur von anderen Autos und nicht von Menschen gefahren werden?

Das Konvertieren eines Bitblobs (einige ASCII-Werte) in einen anderen Bitblob (einige Maschinencodes) ist nur eine Programmieraufgabe. Die für diese Aufgabe verwendete Programmiersprache ist die von Ihnen gewünschte. Sie können und es gab Assembler, die in vielen verschiedenen Sprachen geschrieben wurden.

Neue Sprachen können anfänglich nicht in ihrer eigenen Sprache geschrieben werden, da es für sie noch keinen Compiler / Assembler gibt. Wenn es keinen Compiler für eine neue Sprache gibt, müssen Sie den ersten in einer anderen Sprache schreiben und dann booten, wenn dies überhaupt Sinn macht. (html und ein Webbrowser, ein Programm, das ein paar Bits aufnimmt und ein paar ausspuckt, wird niemals in html geschrieben, kann nicht sein).

Muss keine neue Sprache sein, kann eine existierende sein. Neue C- oder C ++ - Compiler kompilieren sich nicht automatisch von Anfang an.

Für Assembler und C die ersten beiden Sprachen für fast alle neuen oder geänderten Befehlssätze. Wir sind nicht in der Vergangenheit, wir sind in der Gegenwart. Wir können leicht einen Assembler in C oder Java oder Python oder was auch immer für jeden Befehlssatz und jede Assemblersprache generieren, die wir wollen, auch wenn sie noch nicht existieren. Ebenso gibt es viele unvergessliche C-Compiler, mit denen wir Assemblersprachen für jede gewünschte Assemblersprache ausgeben können, auch wenn der Assembler noch nicht vorhanden ist.

Genau das machen wir mit einem neuen Befehlssatz. Nehmen Sie einen Computer, auf dem unser neuer Befehlssatz nicht ausgeführt wird, mit seinem C-Compiler, der weder für unseren neuen Befehlssatz noch für seinen Assembler kompiliert wurde, und erstellen Sie einen Cross-Assembler und einen Cross-Compiler. Entwickeln und verwenden Sie dies, während Sie die Logik erstellen und simulieren. Durchlaufen Sie die normalen Entwicklungszyklen, indem Sie einen Fehler finden und beheben und erneut testen, bis im Idealfall alle Tools und die Logik als bereit erachtet werden. Und je nach Ziel, beispielsweise wenn es sich um einen Mikrocontroller handelt, der kein Betriebssystem ausführen kann, hätten Sie niemals einen Grund, dies so zu booten, dass die Toolchain mithilfe des nativen Befehlssatzes generiert und ausgeführt wird. Sie würden immer überqueren kompilieren. Anders als bei einer Wayback-Maschine ist es niemals sinnvoll, den Assembler in Assembler zu schreiben.

Ja, wenn Sie zurückgehen oder so tun könnten, als ob Sie zurückkehren würden, war der erste Assembler ein Mensch mit Bleistift und Papier, der etwas schrieb, das für sie Sinn machte, und dann die Teile daneben schrieb, die für die Logik Sinn machten. Verwenden Sie dann Schalter oder einen anderen Weg, um die Bits in die Maschine zu bekommen (google pdp8 oder pdp11 oder altair 8800) und sie dazu zu bringen, etwas zu tun. Es gab anfangs keine Computersimulatoren. Man musste nur die richtige Logik finden, indem man lange genug darauf starrte oder auch mehrere Umdrehungen des Chips drehte. Die Tools sind heutzutage gut genug, um A0-Erfolge zu erzielen, da das Ding mehr als nur ein großer Widerstand ist. Vieles davon funktioniert. Möglicherweise benötigen Sie noch einen Dreh für Dinge, die Sie nicht vollständig simulieren konnten erster SPI, ohne auf den dritten oder vierten Spin warten zu müssen,

Wie zu erwarten, nehmen Sie auf Ihrem Wayback-Computer Ihren handgefertigten Code und verwenden ihn, um ein Programm von Band oder Karte zu laden. Sie geben einem Assembler auch einen Code in Maschinencode, der möglicherweise nicht vollständig ist, sondern das Programmieren ein wenig erleichtert. Dann wird dieses Werkzeug verwendet, um ein Werkzeug zu erstellen, das eine fortgeschrittenere oder kompliziertere Sprache (einen Makro-Assembler) beherrscht, und dieses Werkzeug wird verwendet, um eine kompliziertere Sprache zu erstellen, und Sie erhalten FORTRAN oder BASIC oder B oder was auch immer. Und dann denken Sie über Bootstrapping in derselben Sprache nach und schreiben den Cross-Compiler neu, um ein nativer Compiler zu werden. Dazu benötigen Sie im Idealfall eine Umgebung oder ein Betriebssystem.

Wenn wir Silizium erzeugen oder testen, müssen wir die Signale betrachten, bei denen es sich um Einsen und Nullen handelt. Die Werkzeuge zeigen uns standardmäßig binär oder hexadezimal an, und es ist mit einigen Werkzeugen möglich, sogar nachzuschlagen, damit die Werkzeuge einige Mnemoniken (vielleicht Assembler) anzeigen, aber oft können die Ingenieure (Silizium / Hardware und Software) entweder genug davon lesen Den Maschinencode oder die Demontage / Auflistung verwenden, um die Anweisungen zu "sehen".

Je nachdem, was Sie gerade tun, schieben Sie möglicherweise nur einen Teil des Maschinencodes in die Testvektoren, anstatt den Test neu zu schreiben und neu zu kompilieren oder neu zusammenzusetzen. Wenn Sie beispielsweise über eine Pipeline verfügen und in einer bestimmten Tiefe vorab abrufen, müssen oder möchten Sie möglicherweise nach dem Ende des Programms einige Nops oder andere echte Anweisungen einfügen, damit die Pipe bei nicht definierten Anweisungen nicht kotzt, und Sie können sich einfach dafür entscheiden Füllen Sie den Maschinencode in der Liste / Datei auf der niedrigsten Ebene aus, anstatt den Compiler, Assembler oder Linker dazu zu bringen.

Während Sie den Prozessor testen, müssen Sie sich natürlich mit undefinierten und möglicherweise nicht interessierenden Bits usw. befassen. Sie müssten also in den Maschinencode gehen und ein oder mehrere spezifische Bits in einer Anweisung in einem normal funktionierenden Programm ändern. Lohnt es sich, ein Programm dafür zu schreiben oder es einfach von Hand zu machen? Ebenso möchten Sie beim Testen von ECC ein oder mehrere Bits umdrehen und feststellen, dass sie korrigiert oder eingeklemmt werden. Zugegeben, es ist viel einfacher, ein Programm zu schreiben, oder man könnte es einfach von Hand machen.

Dann gibt es natürlich Sprachen, die keinen Code produzieren, der auf einem Prozessor, Early Pascal, Java, Python usw. ausgeführt wird. Sie benötigen eine VM, die in einer anderen Sprache geschrieben ist, um diese Sprachen zu verwenden. Sie können Ihren Java-Compiler nicht verwenden, um ein Java-VM zu erstellen, da dies aufgrund des Sprachdesigns keinen Sinn ergibt.

(Ja sicher, nach der reinen Implementierung dieser Sprachen erstellt irgendwann jemand ein unreines Backend, das manchmal auf echte Befehlssätze abzielt, nicht auf den vm-Befehlssatz. In diesem Fall können Sie die Sprache verwenden, um sich selbst oder seine vm zu kompilieren, wenn Sie das wirklich spüren brauchen. Ein Gnu Java Frontend zu gcc zum Beispiel).

Im Laufe der Zeit und wahrscheinlich immer noch schreiben wir keine C-Compiler in C. Wir verwenden Dinge wie Bison / Flex oder eine andere Programmiersprache, die wir verwenden, um das C für uns zu generieren, das wir nicht selbst schreiben wollten. Ein gewisser Prozentsatz ist in C sicher, aber ein gewisser Prozentsatz ist in einer anderen Sprache, die einen anderen Compiler verwendet, der Bits eingibt und andere Bits ausgibt. Manchmal wird dieser Ansatz auch zum Generieren eines Assemblers verwendet. Bis zum Designer des Compilers / Assemblers (Programme, die eine Aufgabe haben, Bits einzugeben und dann andere Bits auszugeben), wie sie es implementieren werden. Die programmgenerierten Parser könnten sicher von Hand programmiert werden, was nur zeitaufwendig ist, sodass die Leute nach einer Verknüpfung suchen. Genau wie Sie einen Assembler in Assembler schreiben könnten, aber die Leute suchen nach einer Abkürzung.

Ein Webbrowser ist nur ein Programm, das einige Bits aufnimmt und andere ausspuckt. Ein Assembler ist nur ein Programm, das einige Bits aufnimmt und andere ausspuckt. Ein Compiler ist nur ein Programm, das einige Bits aufnimmt und andere ausspuckt. Für all diese gibt es einen dokumentierten Regelsatz für die Eingangs- und Ausgangsbits für jede Programmieraufgabe. Diese Tasks und Bits sind so allgemein, dass jede VERFÜGBARE Programmiersprache verwendet werden kann. Der Schlüssel ist hier verfügbar. Holen Sie sich das Linux von Grund auf neu und probieren Sie es aus. Probieren Sie einen PDP8 oder PDP11 oder Altair 8800 oder einen anderen Simulator mit einer simulierten Frontplatte aus.

Oldtimer
quelle