sabato 29 novembre 2008

Kernel MINIX : I Parte

HTML clipboard
Che cos'è un sistema operativo?cosa fa?
Per capire come sia fatto analizziamo il codice sorgente del cuore(kernel) di un sistema operativo minimale Minix.
Per semplicità di trattazione si prende in considerazione una delle prime versioni del kernel creata da Andrew S. Tanenbaum ,intel 1.1.
Le prime versioni erano fortemente legate all'architettura sottostante. Successivamente si sono resi conto che potevano utilizzare istruzioni condizionali (#if,#ifndef,#endif).
Minix implementa le strutture dati e i processi atti ad offrire servizi minimali del sistema operativo quali gestione dei thread,spazi di indirizzamento o comunicazione interprocesso.E' arrivato alla versione 3.1.3.

Personalmente analizzerò ogni singolo file,data l'ampiezza dell'argomento,anche se cercherò di essere più sintetico possibile, dividendo la descrizione in più' post.
In questa prima "puntata" definisco tutte le principali strutture dati che servono per descrivere il sistema.

type.h
Il file type.h contiene la definizione dei tipi utilizzati dal kernel e sono presenti alcune macro. In dettaglio abbiamo:
- le variabili utilizzate dal file system(numero blocchi,inode,zona,link,posizione del file,tipe,id dell'utente e del gruppo)
unshort block_nr; /* block number,assenza,max */
unshort inode_nr; /* inode number,assenza,max */
typedef unshort zone_nr; /* zone number,assenza 0,max 0177777(base ottagonale) */
typedef long zone_type; /* zone size */
typedef unshort dev_nr; /* major | minor device number ,no dev*/
typedef char links; /* number of links to an inode,max */
typedef long real_time; /* real time in seconds since Jan 1, 1980 */
typedef long file_pos; /* position in, or length of, a file */
typedef short int uid; /* user id */
typedef char gid; /* group id */

- locazioni gestite dal gestore della memoria, quali: informaizoni sull' indirizzo vituale e fisico.Un Click è 256bytes
typedef unsigned vir_bytes; /* virtual addresses and lengths in bytes */
typedef unsigned vir_clicks; /* virtual addresses and lengths in clicks */
typedef long phys_bytes; /* physical addresses and lengths in bytes */
typedef unsigned phys_clicks; /* physical addresses and lengths in clicks */
typedef int signed_clicks; /* same length as phys_clicks, but signed */

- per le comunicazioni interprocesso sono definiti 6 tipi di messaggio. La struttura del messaggio


- la struttura dati per mappare indirizzi virtuali ad indirizzi fisici e quella per la copia tra locazioni di memoria
struct mem_map {
vir_clicks mem_vir; /* virtual address */
phys_clicks mem_phys; /* physical address */
vir_clicks mem_len; /* length */
};

copy_info { /* used by sys_copy(src, dst, bytes) */ };

signal.h
signal.h contiene 16 costanti per la definizione dei segnali(
kill,bus error,float point exception,alarm clock,bad argument to system call,trace trap,Termination sw, stack fault.)
#define NR_SIGS 16 /* number of signals used */

Const.h
Definisce le costanti utilizzate dal kernel i, in particolare notiamo:
1. la memoria per i processi è divisa in 3 parti
  1. Text, dove vengono memorizzate tutte le istruzioni del processo
  2. Data, quì vengono memorizzati i dati statici e le costanti
  3. Stack, pila di chiamate a subroutine
#define PRIVATE static /* PRIVATE x limits the scope of x */
#define PUBLIC /* PUBLIC is the opposite of PRIVATE */
#define HZ 60 /* clock freq (software settable on IBM-PC) */
#define BLOCK_SIZE 1024 /* # bytes in a disk block */
#define SUPER_USER (uid) 0 /* uid of superuser */
#define NR_TASKS 8 /* number of tasks in the transfer vector */
#define NR_PROCS 16 /* number of slots in proc table */
#define T 0 /* proc[i].mem_map[T] is for text */
#define D 1 /* proc[i].mem_map[D] is for data */
#define S 2 /* proc[i].mem_map[S] is for stack */

2. Il processo numero zero e' quello resposabile alla gestione della memoria.
Nella tabella dei proc, in seconda posizione(posizione 1 del vettore) troviamo il processo file system e in terza posizione quello per la gestione utenti.
Minix è un sistema multi utente.
/* Process numbers of some important processes */
#define MM_PROC_NR 0 /* process number of memory manager */
#define FS_PROC_NR 1 /* process number of file system */
#define INIT_PROC_NR 2 /* init -- the process that goes multiuser */

3. sono presenti informazioni utili al file system, in particolare per utilizzare gli inode. Sono presenti le costanti per distinguere se una operazione è di lettura o scrittura, l'ampiezza della parola, la lunghezza massima del path, i tipi di file e per i diritti su di essi.
#define LOW_USER 2 /* first user not part of operating system */
#define TO_USER 0 /* flag telling to copy from fs to user */
#define FROM_USER 1 /* flag telling to copy from user to fs */
#define READING 0 /* copy data to user */
#define WRITING 1 /* copy data from user */
#define WORD_SIZE 2 /* number of bytes per word */
#define MAX_PATH 128 /* max length of path names */
#define MAX_ISTACK_BYTES 1024 /* maximum initial stack size for EXEC */
#define ROOT_DEV (dev_nr) 256 /* major-minor device number of root dev */
#define BOOT_DEV (dev_nr) 512
/* for inode */
#define I_TYPE 0170000 /* this field gives inode type */
#define I_REGULAR 0100000 /* regular file, not dir or special */
#define I_BLOCK_SPECIAL 0060000 /* block special file */
#define I_DIRECTORY 0040000 /* file is a directory */
#define R_BIT 0000004 /* Rwx protection bit */
#define W_BIT 0000002 /* rWx protection bit */
#define X_BIT 0000001 /* rwX protection bit */
#define I_NOT_ALLOC 0000000 /* this inode is free */

com.h
In questo file sono definite le costanti e gli identificatori di funzioni utilizzati per la comunicazione tra processi.
/* Task numbers, function codes and reply codes. */
#define SEND 1 /* function code for sending messages */
#define RECEIVE 2 /* function code for receiving messages */
#define BOTH 3 /* function code for SEND + RECEIVE */
#define ANY (NR_PROCS+100) /* receive(ANY, buf) accepts from any source */
#define HARDWARE -1 /* used as source on interrupt generated msgs */
#define TTY -7 /* terminal I/O class */
#define PRINTER -8 /* printer I/O class */
#define FLOPPY -5 /* floppy disk class */
#define WINCHESTER -6 /* winchester (hard) disk class */
#define MEM -4 /* /dev/ram, /dev/(k)mem and /dev/null class */
#define CLOCK -3 /* clock class */
#define SYSTASK -2 /* internal functions */(forl,exit,copy,exec,abort,time)

error.h
Sono presenti tutti i codici di errori.

call.h
Contiene gli identificatori di tutte le system call consentite, 5 identificatori di funzioni che non sono chiamate di sistema ma vengono processate come tali.
#define NCALLS 69 /* number of system calls allowed */
#define EXIT 1
#define FORK 2
#define READ 3
#define WRITE 4
#define OPEN 5
#define CLOSE 6
#define CHROOT 61
/* The following are not system calls, but are processed like them. */
#define KSIG 64 /* kernel detected a signal */

sgtty.h
Contiene le strutture dati per le operazioni di I/0 su file(stream,tty, nastro, disco di file, ecc)
sgttyb = { char sg_ispeed; /* input speed (not used) */
char sg_ospeed; /* output speed (not used) */
char sg_erase; /* erase character */
char sg_kill; /* kill character */}
struct tchars {
char t_intrc; /* SIGINT char */
char t_quitc; /* SIGQUIT char */
char t_startc; /* start output (initially CTRL-Q) */
char t_stopc; /* stop output (initially CTRL-S) */
char t_eofc; /* EOF (initially CTRL-D) */
char t_brkc; /* input delimiter (like nl) */
};

stat.h
per le statistiche sugli inode. es. tipo del file,se è una directory, dimensione, data creazione.etc.

stdio.h
Descrive le funzioni, attraverso macro, standard I/O e definisci la struttura dati per il file.
Gli stream sono file con associato il buffer ed il puntatore che definisce il tipo.
Le funzioni di i/o più comuni prelevano o inseriscono caratteri dalla stream.
#define BUFSIZ 1024
#define NFILES 20
#define NULL 0
#define EOF (-1)
#define CMASK 0377
#define READMODE 1
#define WRITEMODE 2
#define UNBUFF 4
#define _EOF 8
#define _ERR 16

#ifndef FILE
extern struct _io_buf {
int _fd;
int _count;
int _flags;
char *_buf;
char *_ptr;
} *_io_table[NFILES];
#endif /* FILE */

#define getchar() getc(stdin)
#define putchar(c) putc(c,stdout)
#define puts(s) fputs(s,stdout)
#define fgetc(f) getc(f)
#define noperprintf(p) ((p)->_flags &= ~PERPRINTF)
#define perprintf(p) ((p)->_flags |= PERPRINTF)//If you want a stream to be flushed after each printf use:


regexp.h
Vi sono sotto routine per expressioni regolari e la strtuttra dati regexp migliora il patter matching

ctype.h
Sono definite delle macro utilizzabili per ricavare il tipo del carattere
se è alfanumerico,maiuscolo,minuscolo,compreso tra [0-9],spazio,etc.
#define isalpha(c) ((_ctype_+1)[c]&(_U|_L))
#define isupper(c) ((_ctype_+1)[c]&_U)
#define islower(c) ((_ctype_+1)[c]&_L)
#define isdigit(c) ((_ctype_+1)[c]&_N)

lib.h
Funge da unificatore dei vari header e definisce il vettore delle eccezioni
extern int begsig(); /* interrupts all vector here */

blocksize.h
Contiene solo la costante per la dimensione del blocco dati utilizzato dal fylesustem
#define BLOCK_SIZE 1024 /* file system data block size */

pwd.h
Viene definita la struct per la password
char *pw_name /* user's login name */
uid_t pw_uid /* numerical user ID */
gid_t pw_gid /* numerical group ID */
char *pw_dir /* initial working directory */
char *pw_shell /* program to use as shell */

grp.h
password per il gruppo
char gr_name /* the name of the group */
char *passwd;
gid_t gr_gid /* numerical group ID */

setjmp.h
Specifica il buffer che si occupa di salvare lo stato corrente delle CPU
#define _JBLEN 3
typedef int jmp_buf[_JBLEN];

package KERNEL
type.h
Contiene la struttura dati pc_psw, dipendente dalla macchina, che è utilizzata per salvare sullo stack, il program counter e i registri in caso di trap o interruzione per poi ripristinarli.
program status word è il registro contiene i bit che specificano il livello di privileggio con cui il processore sta eseguendo e il bit che abilità il processore a ricevere l'interruzioni
struct pc_psw {
int (*pc)(); /* storage for program counter */
phys_clicks cs; /* code segment register */
unsigned psw; /* program status word */
};

glo.h
Variabili globali utilizzati dal kernel per per il clock e il timer, processi,segnali, messaggi, il tipo della CPU, lo stack del kernel e dei task. Ogni task ha uno stack di 256 bytes ed il kernel a un solo stack di 256bytes
EXTERN real_time realtime; /* real time clock */
EXTERN int cur_proc; /* current process */
EXTERN int prev_proc; /* previous process */
EXTERN int olivetti; /* TRUE for Olivetti-style keyboard */
EXTERN int pc_at; /* PC-AT type diskette drives (360K/1.2M) */
EXTERN struct t_stack {
int stk[TASK_STACK_BYTES/sizeof(int)];
} t_stack[NR_TASKS - 1]; /* task stacks; task = -1 never really runs */

EXTERN char k_stack[K_STACK_BYTES]; /* The kernel stack. */

const.h
Costanti usati dal kernel.
  • Ogni processo usa 11 registri generali. In ordine sono : ax, bx, cx, dx, si, di, bp, es, ds, cs, ss
    • AX (Accumulatore): Utilizzato nelle istruzioni aritmetiche e di I/O
    • BX (Base Register): Può essere utilizzato come registro base per il calcolo di indirizzi
    • CX (Count Register): Utilizzato tipicamente come contatore
    • DX (Data Register): Utilizzato in operazioni di I/O, moltiplicazione, divisione con numeri a 32 bit (in coppia con AX)
    • SP (Stack Pointer): Puntatore alla cima dello stack
    • BP (Base Pointer): Utilizzato come puntatore all’interno dello stack ma può
    • essere impiegato amche come generico registro indice
    • SI (Source Index): Registro sorgente o come generico registro indice.
    • DI (Destination Index): Registro destinazione
    • CS Segmento codice.
    • DS Segmento dati.
    • ES Segmento extra.
    • SS Segmento stack.
  • il valore iniziale dello stack pointer è 0x0010 dato che 3 parole sono inserite dal kernel
#define NR_REGS 11 /* number of general regs in each proc slot */
#define INIT_SP (int*)0x0010 /* initial sp: 3 words pushed by kernel */ Stackpointer
  • i seguenti identificatori di registri sono mappati nel codice assembly. Difatti un warning avvisa di non cambiarli senza cambiare il valore corrispondente nel codice assembly.

#define ES_REG 7 /* proc[i].p_reg[ESREG] is saved es */
#define DS_REG 8 /* proc[i].p_reg[DSREG] is saved ds */
#define CS_REG 9 /* proc[i].p_reg[CSREG] is saved cs */
#define SS_REG 10 /* proc[i].p_reg[SSREG] is saved ss */

  • - E' definito il vettore delle interruzioni per gli interrupt generati da orologio,tastiera,floppy,stampante,system call,I/0 e divisione.Sono presenti maschere interruzioni, per abilitare e disabilitare alcuni bit

#define INT2_MASK 0xA1 /* setting bits in this port disables ints */
#define ENABLE 0x20 /* code used to re-enable after an interrupt */
#define IDLE -999 /* 'cur_proc' = IDLE means nobody is running */

  • Tre differenti code per lo scheduling,una per i task, una per i processi server(offre servizi di sistema a livello utente) e l'altra per i processi utente.

#define NQ 3 /* # of scheduling queues */
#define TASK_Q 0 /* ready tasks are scheduled via queue 0 */
#define SERVER_Q 1 /* ready servers are scheduled via queue 1 */
#define USER_Q 2 /* ready users are scheduled via queue 2 */

la funzione print del kernel
#define printf printk /* the kernel really uses printk, not printf */

proc.h
In questo file è dichiarato la tabella dei processi(array di processi), con i puntatori necessari a gestirla(processo corrente, processo in testa e coda),ed i puntatori alle liste pronti relative alle tre code di scheduling. Viene fatto riferimento a codice assembly. Si noti che anche i task fanno parte della tabella.

La struttura processo contiene, nel nosto caso,
- 11 registri, con relativo mapping in memoria,
- il puntatore stack pointer,
- la struttura dati per salvare il pc e i registri in caso di interruzione,
- un flag per lo stato del processo,
- il valore più piccolo consentito dello stack,
- l'id del processo(pid),
- l'ora
- puntatore alla lista processi destinatari,
- puntatore al buffer del messaggio,
- puntatore il processo che vuole comunicare con me e puntatore al prossimo processo in stato di pronto

EXTERN struct proc {

int p_reg[NR_REGS]; /* process' registers */
int *p_sp; /* stack pointer */
struct pc_psw p_pcpsw; /* pc and psw as pushed by interrupt */
int p_flags;/* P_SLOT_FREE, SENDING, RECEIVING,etc,if =0 runnable*/
struct mem_map p_map[NR_SEGS];/* memory map */
int *p_splimit; /* lowest legal stack value */
int p_pid; /* process id passed in from MM */
...
struct proc *p_callerq;/* head of list of procs wishing to send */
message *p_messbuf; /* pointer to message buffer */
int p_getfrom; /* from whom does process want to receive? */
struct proc *p_nextready; /* pointer to next ready process */
} proc[NR_TASKS+NR_PROCS];

dmp.c
Contiene informazioni utili per il debugging. Stampa tutte le informazione sul processo in esecuzione(pid,stato,etc..)
table.c
Contiene tutti i dati definiti nei vari Header, fa il merge di tutte le strutture dati

Daniele Licari

venerdì 28 novembre 2008

Architettura di un Compilatore


Il comportamento di un compilatore può essere schematizzato in due fasi: front end e back end.

Prendiamo per esempio il seguente programma.
int main(){
int i = 0;
}
Stadio di front end
Analisi lessicale : Attraverso un'analizzatore lessicale, spesso chiamato scanner o lexer, il compilatore divide il codice sorgente in tanti pezzetti chiamati token.
Lo scanner legge una sequenza di caratteri ('i','n','t',' ','m','a','i','n','(',')','{',etc.)
rimuove i commenti e spazi bianchi e crea una sequenza di tokens('int','main','(',')','{','int',etc.)
I token sono gli elementi minimi (non ulteriormente divisibili) di un linguaggio, ad esempio parole chiave (for, while), nomi di variabili (pippo), operatori (+, -, <<).
Analisi sintattica :
L'analisi sintattica prende in ingresso la sequenza di token generata nella fase precedente ed esegue il controllo sintattico che verifica se i la grammatica(insieme di regole) definita per un linguaggio e' rispettata.
Es.le keyword devono precedere gli identificatori
Analisi semantica :
L'analisi semantica si occupa di controllare il significato del progranna. Es. riconosce le occorenze di uno stesso identificatore, viene effettuato il type checking,controllare che gli identificatori siano stati dichiarati prima di essere usati,etc.
Come supporto a questa fase viene creata una tabella dei simboli (symbol table) che contiene informazioni su ogni identificatore presente nel programma,es. nome, scope, tipo etc.
Non tutte le regole semantiche sono applicate a compile time. Per esempio riferimenti a oggetti validi deve essere fatto a run-time
Questo processo di cotnrollo statico della semantica produce l'albero sintattico astratto (AST).
Ogni nodo dell' AST è arricchito con delle informazioni chiamate annotazioni, quali i puntatori a identificatori alla Symbol Table.
In questo stadio può essere presente una prima fase di ottimizzazione

Stadio di back end
Anche lo stadio di back end si divide in più fasi:
Generazione del codice intermedio: Dall'albero di sintassi viene generato il codice intermedio.
Possono essere fatti ottimizazioni al codice intermedio.
Generazione del codice target: In questa fase il compilatore traduce il codice intermedio nella forma del linguaggio target(assembly o macchina).
Per generare codice assembly o il linguaggio macchina il generatore di codice accede alla symbol table per assegnare locazioni a variabili e attraversando l'albero sintattico astratto genera le istruzioni(load,store,aritmetico-logiche,etc) relative al programma.

Ref.
Programming Launguage Pragmatics, Michael L. Scott, University of Rochester


Daniele Licari

mercoledì 26 novembre 2008

Compilazione vs Interpretazione

Ad un livello molto alto di astrazione possiamo considerare la compilazione ed l'esecuzione di un programma scritto in un linguaggio di alto livello, come segue:

Programma(Codice) Sorgente → Compilatore → Programma Target
Input → Programma Target → Output

Il compilatore traduce il programma di alto livello in uno equivalente Target( tipicamente in linguaggio macchina ).
Il compilatore utilizzato nei sistemi operativi produce object files che oltre al linguaggio macchina contiene informazioni per il runtime, riallocazione, stack unwinding, commenti, tabella dei simboli (nomi di variabili e funzioni) per il linking e/o debugging.

Uno stile alternativo d' implementazione per i linguaggi di altro livello è l' interpretazione

(Programma(Codice) Sorgente, Input ) → Interpretazione → Output

Nei programmi interpretati le decisioni vengono prese a run-time(tempo di esecuzione), a regola una decisione presa a compile-time(a tempo di compilazione) non deve essere ri-presa a run-time; ne' segue che, generalmente, la compilazione e' più' performante dell'interpretazione.
Es.
Il compilatore può' garantire che una variabile x ha una indirizzo di memoria '0xF' e genera il linguaggio macchina in modo tale che quando il programma sorgente si riferisce a x lo trova sempre nella locazione di memoria '0xF'.
Di contro un interprete ha bisogno di accedere ad una tabella, ogni volta che vuole utilizzare x, per trovare la sua locazione.


Un interprete si occupa anche della dell'esecuzione del programma sorgente per mezzo di una macchina intermedia(virtuale) che a run-time legge uno o più statements alla volta e li compila o interpreta in linguaggio macchina.
In questo caso siamo di fronte ad una implementazione mista.

Programma(Codice) Sorgente → Compilazione → Programma Intermedio
(Programma Intermedio, Input ) → Macchina Virtuale → Output


Alcuni linguaggio che usano la virtual machine sono : Java, C#, VB.net, Python, Perl.
Nel caso in cui la macchina intermedia coincide con la macchina concreta abbiamo l traduzione pura, es. l'assembler.


Nella maggior parte dei linguaggi interpretati viene eseguita una fase iniziali(preprocessor) nella quale vengono eliminati i commenti,gli spazi vuoti, insiemi di caratteri vengono trasformati in tokens (keyword, identificatori, numeri, simboli) e vengono espanse le macro.
Nelle prime versioni di Basic il manuale suggeriva di eliminare i commenti per migliorare le performance.

I linguaggi di alto livello che utilizzano la compilazione pura, in genere contano sull'esistenza di librerie di sotto-routines che non sono parte del programma originario. Es. includono funzioni matematiche o di I/O.
Il compilatore si appoggia su un programma separato chiamato linker, che compone le librerie appropriate al programma finale
Es. Linker

programma → Compilatore → Linguaggio macchina incompleto
(Linguaggio macchina incompleto,Librerie di routunes ) → Linker → Linguaggio macchina completo.

Molti compilatori generano il codice assembly anziche' codice macchina per facilitarne il debugger dato che piu' facile da leggere.
Es. Compilatore C++

Programma Sorgente → Preprocessor → Programma Sorgente Modificato (Espande macro, toglie spazi)
Programma sorgente Modificato → Compilatore c++ → Codice C
Codice C → Compilatore C → Linguaggio Assembly
Linguaggio Assembly → Assembler → linguaggio macchina

In molte macchine l'insieme di istruzioni assembly non sono implementato ad hardware ma vengono interpretate, dall'interprete firmware, in istruzioni di basso livello chiamate “micro istruzioni” implementate ad hardware.
Di conseguenza abbiamo il seguente schema:

Linguaggio Assembly → Assembler → Microlinguaggio
Microlinguaggio → Firmware → Linguaggio macchina


Ref.
Programming Launguage Pragmatics, Michael L. Scott, University of Rochester

slide, http://www.di.unipi.it/~levi/corsoMP/corso2/sld010.htm

Daniele Licari

martedì 25 novembre 2008

Breve storia sui linguaggi di programmazione


I primi calcolatori elettronici, nati nel 1940, erano enormi, riempivano una stanza, consumavano tantissima energia e costavano milioni di dollari.
A quel tempo i programmatori scrivevano codice direttamente in linguaggio macchina.
Il linguaggio macchina è una sequenza di bit direttamente controllato da processore per eseguire istruzioni di add, compare e move sui dati.

Es. la somma tra due registri in linguaggio macchina
0000 00ss ssst tttt dddd d000 0010 0000

Successivamente nasce l'assembly ideato per consentire di esprimere operazioni con abbreviazioni menmoniche.

Es. la somma tra due registri in linguaggio assembly
add $d, $s, $t
MIPS Instruction Reference

Inizialmente il linguaggio assembly era caratterizzato da una corrispondenza uno ad uno tra istruzioni menmoniche e istruzioni linguaggio macchina.
Ognuna dell’istruzione assembly veniva codificata nell'istruzione corrispondente del linguaggio macchina.
La traduzione( interpretazione ) tra linguaggio menmonico e linguaggio macchina è affidato ad un processo di sistema chiamato assemblatore(assembler).
Con lo sviluppo dei computers diventava veramente frustrante per i programmatori doversi riscrivere il programma in ogni assembly per poterlo far eseguire su architetture diverse.
Così matura l'idea di avere un linguaggio indipendente dalla macchina.
L'obbiettivo viene raggiunto negli anni '50 con Fortran il primo linguaggio d’alto livello indipendente dalla macchina, seguirono altri linguaggio con Lisp ed Angol.
La traduzione da linguaggio d’alto livello in linguaggio assembly o linguaggio macchina è effettuata da un processo di sistema chiamato compilatore(compiler).
Il compilatore è, sostanzialmente, più complicato dell'assembler, la corrispondenza uno a uno con le istruzioni del linguaggio macchina non esiste più quando il sorgente è di alto livello. Ricordiamo che il linguaggio assembly e' soltanto un'interpretazione menmonica del linguaggio macchina e può essere scavalcato in modo da far produrre direttamente al compilatore linguaggio macchina, ma si preferisce far generare codice assembly per comodità e poter utilizzare più facilmente il debugger.
Inizialmente Fortran non ha avuto tanto successo perché i programmatori preferivano scrivere programmi più efficienti direttamente in assembly, ma con lo sviluppo dei computer, il crescere della complessità dell'hardware e il miglioramento della tecnologia dei compilatori si è arrivato al punto che il codice generato dalla compilazione risulta essere migliore da quello implementato da un sviluppatore assembly.

Ref.
Programming Launguage Pragmatics, Michael L. Scott, University of Rochester

Daniele Licari

lunedì 24 novembre 2008

Il polimorfismo Ad-hoc( o non universale )

Le funzioni che utilizzano il polimorfismo Ad-hoc possono eseguire codice differente per tipi differenti di argomenti e di conseguenza possono comportarsi in modo diverso a secondo del tipo.
Ci sono due generi di polimorfismo ad-hoc overloading("sovraccarico") e coercion("coercisione")

Nell' overloading viene utilizzato lo stesso nome di funzione per denotare funzioni differenti, e si utilizza il contesto(tipo parametri,tipo ritorno,..) per decidere a quale funzione riferisce una particolare istanza del nome.
Un preprocessing del programma andrà ad eliminare l’overloading assegnando nomi differenti alle diverse funzioni.
Es.
void print(int x)
void print(String x)

Vegono trasformati dal preprocessore in
void print_Int(int x)
void print_String(String x)

In questo senso l’overlodiang risulta essere una convenzione sintattica.

La coercion e’ invece una operazione semantica necessaria per convertire l’argomento di una funzione al tipo previsto per la stessa, in situazioni che potrebbero portare ad un errore sui tipi.
La coercion può' essere prevista staticamente, a compile-time, o dinamicamente, dà test a run-time sugli argomenti delle funzioni.

Il polimorfismo Ad-hoc, in questo esempio, può’ essere spiegato in tre modi differenti
Es. Abbiamo le seguenti espressioni:
3 + 4
3.0 + 4
3 + 4.0
3.0 + 4.0

1. l’ operando “+” e’ sovraccaricato quattro volte , uno per ogni combinazione di tipi di argomenti
2. l’ operando “+” e’ sovraccaricato due volte, quando l’argomento è di tipo intero o reale e l’argomento intero e “coerciso” al tipo reale
3. l’operatore “+” e’ definito solo per la somma sui reali e gli argomenti interi sono sempre “coercisi” ai corrispondenti reali

Bibliografia
[CW85] Luca Cardelli, Peter Wegner, "On Understanding Types, Data Abstraction, and Polymorphism", ACM Computer Surveys, December 1985.

Daniele Licari