http://llvm.org/releases/3.4/docs/WritingAnLLVMBackend.html [versione 3.4, l'ultimissima]e non sono d'accordo che LLVM abbia poca documentazione:http://llvm.org/releases/3.4/docs/index.htmlIo dico: prima ci si legge quella guida (che tra l'altro fa esempi con il backend Sparc), poi dare un'occhiata a un backend già implementato (uno di qualche RISC che sarà più semplice), e poi provare a mettere giù quello personalizzato. Ovviamente per quel tempo si dovrebbe avere ISA, simulatore e assembler già fatti, finiti e finalizzati.
Ecco, questa è la fregatura: ti serve anche un assembler. Pensavo bastasse descrivere l'architettura, con tanto di opcode, e poi sarebbe stato il backend di LLVM a generare direttamente il codice binario...
A me interessava esclusivamente generare il binario, e generare delle statistiche sulla lunghezza del codice e delle istruzioni emesse.
Per questa fase va bene così, tanto lavoro in asm su un solo file alla volta che posso spezzare in file .inc da includere sfruttando #include "file.inc" esattamente come si fa in C, pero' supportare il formato obj sarà una feature da aggiungere appena possibile, in questo modo avrò una toolchain funzionante nel modo giustoC to asm -> asm to obj -> all obj to bin
Citazione da: cdimauro - 19 Gennaio 2014, 23:36:43Come puoi intuire, la mia è lunghezza variabile, con opcode anche ben più lunghi dei tuoi 8 byte, ma nonostante tutto è molto facile da decodificareno, per me e' un casino da gestire in quel modo, ho fatto molta fatica a scrivere il decode del 68000 nel relativo simulatore e me la sono cavata solo grazie a tecniche di maschera, se analizzi il decoder di un MIPS e' gestito a classi che fissano modelli di pattern ed in quei pattern fanno decodificano cose che sono messe sempre nello stesso modo ed interpretate sempre nello stesso modo, introdurre eccezioni significa in HDL introdurre fsm, il che imputtana tutto il progetto, a cominciare dai vettori di test.difatti mi chiedo come facciano in intel, a me viene il mal di testa gia' con la mia CPU bovina che e' sempliciotta, la loro … richiede molto + mind power
Come puoi intuire, la mia è lunghezza variabile, con opcode anche ben più lunghi dei tuoi 8 byte, ma nonostante tutto è molto facile da decodificare
Citazione da: cdimauro - 19 Gennaio 2014, 22:31:46Ecco, questa è la fregatura: ti serve anche un assembler. Pensavo bastasse descrivere l'architettura, con tanto di opcode, e poi sarebbe stato il backend di LLVM a generare direttamente il codice binario...Citazione da: cdimauro - 19 Gennaio 2014, 23:13:23A me interessava esclusivamente generare il binario, e generare delle statistiche sulla lunghezza del codice e delle istruzioni emesse.Ah ecco, tu hai un'esigenza diversa: io avevo già considerato che, nel progettare una nuova architettura, si avesse già il simulatore e un assembler pronti.Però con LLVM puoi anche fare da assemblatore, e disassemblatore addirittura; ovviamente nella creazione del backend prima arrivi a un punto dove puoi mostrare l'assembly (anche per testare il backend), e poi quando è corretto passi a scrivere il generatore di binario (cioè aggiungere quelle classi che servono).Prova a dare una letta a questa guida:http://www.embecosm.com/appnotes/ean10/ean10-howto-llvmas-1.0.htmlCmq x86 (e forse ARM, ma non sono sicuro) è un'architettura che sfrutta la generazione codice diretta senza assembly intermedio, quindi se hai dubbi nell'implementazione puoi controllare quella.
Citazione da: legacy - 19 Gennaio 2014, 23:53:35Modalita' di indirizzamento che posso usare introducendo pero' cicli di calcolo dovute alle ALU dei EA-(rn) -> NON IMPLEMENTABILEr1(r2) -> EA = reg[r1] * reg[r2]r1(r2) r3 -> EA = (reg[r1] * reg[r2]) + r3Cmq queste sono tentato dal NON implementarle per il motivo che rompono la regolare cadenza con cui vengono scanditi gli opcode in termini di cicli macchina, oltre al fatto che costano delle EA ALU addizionali e quindi + roba da fare testare etc.
Modalita' di indirizzamento che posso usare introducendo pero' cicli di calcolo dovute alle ALU dei EA-(rn) -> NON IMPLEMENTABILEr1(r2) -> EA = reg[r1] * reg[r2]r1(r2) r3 -> EA = (reg[r1] * reg[r2]) + r3
L'effetto collaterale della BRA invece me lo tengo perché mi piace parecchio
Comunque giusto per cronaca, qui si parla quasi di necessità di un codice intermedio per il multitarget, ma in realtà è uno step che si può saltare. Ciò che serve veramente è una AST e poi da quella si genera codice reale. Questo implica riscrivere un backend da zero per ogni architettura (oppure appoggiandosi ad uno esistente spesso duplicando codice), questa soluzione in genere è la migliore per avere il massimo della libertà per lo sfruttamento delle potenzialità di una architettura e per non perdere tempo sullo translate da codice intermedio astratto a codice reale. L'instruction selection comporta un sacco di compromessi nelle scelte
In parte sì, ma io intendo una serializzazione vera e propria di tutto l'albero, cioè la messa su file dell'intera struttura dati invariata. Probabilmente andando nel pratico conviene di più quel metodo, visto che tutti i maggiori compilatori si sono stabilizzati su questo metodo.
; ModuleID = 'base.c'target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128"target triple = "x86_64-pc-linux-gnu"; Function Attrs: nounwind uwtabledefine i32 @foo(i32 %a, i32 %b) #0 { %1 = alloca i32, align 4 %2 = alloca i32, align 4 store i32 %a, i32* %1, align 4 store i32 %b, i32* %2, align 4 br label %3; <label>:3 ; preds = %6, %0 %4 = load i32* %1, align 4 %5 = icmp sgt i32 %4, 0 br i1 %5, label %6, label %11; <label>:6 ; preds = %3 %7 = load i32* %2, align 4 %8 = mul nsw i32 %7, 2 %9 = load i32* %1, align 4 %10 = sub nsw i32 %8, %9 store i32 %10, i32* %2, align 4 br label %3; <label>:11 ; preds = %3 %12 = load i32* %2, align 4 ret i32 %12}; Function Attrs: nounwind uwtabledefine i32 @main() #0 { %1 = alloca i32, align 4 %a = alloca i32, align 4 store i32 0, i32* %1 %2 = call i32 @foo(i32 3, i32 4) store i32 %2, i32* %a, align 4 %3 = load i32* %a, align 4 ret i32 %3}attributes #0 = { nounwind uwtable "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }!llvm.ident = !{!0}!0 = metadata !{metadata !"Ubuntu clang version 3.4-1~exp1 (trunk) (based on LLVM 3.4)"}
int foo(int a, int b){ while(a>0) b = b*2 - a; return b;}int main(){ int a = foo(3,4); return a;}
CitazioneComunque giusto per cronaca, qui si parla quasi di necessità di un codice intermedio per il multitarget, ma in realtà è uno step che si può saltare. Ciò che serve veramente è una AST e poi da quella si genera codice reale. Questo implica riscrivere un backend da zero per ogni architettura (oppure appoggiandosi ad uno esistente spesso duplicando codice), questa soluzione in genere è la migliore per avere il massimo della libertà per lo sfruttamento delle potenzialità di una architettura e per non perdere tempo sullo translate da codice intermedio astratto a codice reale. L'instruction selection comporta un sacco di compromessi nelle scelteVero. Forse si usa molto la tecnica del linguaggio intermedio per modularizzare più facilmente il progetto e renderlo così più mantenibile da team separati?
Anche se, in effetti, non vedo quale sarebbe il problema nell'esportare direttamente l'AST, anche in forma serializzata (XML?) se necessario.