Jag har rotat lite i hur man skriver kompilatorer, mest för att jag för typ 20 år sen hade funderingar på att skriva en korskompilator för att kunna kompilera Z80-kod på min 286:a och på nåt sätt slanga över det till min gamla Spectravideo 328. Det blev aldrig nåt av de planerna men tankarna har suttit i bakhuvudet.
Det finns en intressant "kurs" som heter "Let's build build a compiler" som konstruerar en kompilator för ett pseudospråk, men den är inte så rolig för han ändrar sig lite under resans gång.
http://compilers.iecc.com/crenshaw/
Men den teknik han använder är nästan frånkörd idag.
Jag tror gcc är idag uppbyggd på ett modulärt sätt så att dess dela kan användas för att skriva kompilatorer för vilket språk som helst och som kompileras till vilken assemblerkod som helst.
Om jag nu inte minns fel så använder man först verktyg i stil med flex för att "tokenisera" källkoden (som jag förstått det innebär det i princip att källkoden omvandlas till ett "träd"). Man bryter så att säga ner källkoden i sina beståndsdelar, basera på språkets definition.
Detta träd konverteras sen till nån form av pseudokod, från den skapar man sen valfri assembler och assemblerar.
Det innebär att man använder universella verktyg hela vägen man behöver bara skriva två saker:
1. Språkdefinitionen (för den lexiska analysen och uppdelningen)
2. Hur pesudoassemblern ska konverteras till riktig assembler
Men idag börjar man lite gå ifrån detta eftersom det är svårt att optimera bra, ska kompilatorn kunna optimera måste den ju veta hur processorn är uppbyggd.
Men man skriver i vilket fall som helst själva kompilatorn i högnivåspråk, finns ingen anledning att skriva den i assembler eftersom man kan skriva kompilatorn på en annan (befintlig) plattform och korskompilera.