martedì 6 ottobre 2009

Ragged array ed ottimizzazione new

Qui cerco di unire due argomenti che sono stati trovati nello stesso tempo ma che non necessariamente sono correlati.
Mentre passavo il tempo a programmare qualcosina mi sono imbattuto in un problemino interessante, ho un set di stringhe che conosco a priori, c'è un modo pulito e semplice per creare un array di stringhe tutto allocato in stack? Sicuramente è un problema che molti non si pongono, ma Ada mi ha insegnato che quando è possibile, è sempre meglio allocare in stack.
In C si può raggiungere facilmente questo risultato con:

char *greet[2] = {"Hello","World!"};

E fin qui tutto ok, è un array di stringhe allocato in stack. Come fare in Ada? Ho provato questa soluzione:

Greet : array (1..2) of constant String (1..6) := ("Hello", "World!");

Errore del compilatore! Le stringhe non sono della stessa dimensione, l'unico modo per risolvere è porre uno spazio dopo Hello ("Hello ") in modo da equiparare le stringhe. Problema fastidioso, sopratutto quando si ha a che fare con stringhe di lunghezza varia.

Quando si allocano gli array i row hanno tutte la stessa dimensione, in C non possiamo dichiarare greet[2][]; ma solo greet[][2]; specificando dopo il numero di row (che non è la dimensione di ogni singolo row). Lo stesso problema c'è nei database: puoi inserire facilmente un'infinità di righe, ma aggiungere campi ad una riga è una procedura dispendiosa (infatti è un'operazione che può fare l'admin della tabella).
Tornando alle stringhe, il problema di prima non accade in C perché le stringhe sono NULL based e tutte le sue funzioni si fermano al primo NULL che incontrano, ma anche lì le row hanno tutte la stessa dimensione. In Ada le stringhe non sono NULL based, dato che per dichiararle/allocarle bisogna sempre specificare la lunghezza e gli access type non ci permettono di andare in zone non allocate (evitando i famosi segmentation fault).

Si, ma come risolvere questo fastidioso problema?

Googlando ho scoperto che questo problema non è insolito, anzi è molto comune. Molti linguaggi ad alto livello implementano i ragged array, che possono contenere row di dimensioni diverse. In C e in Ada si potrebbe emulare questo fatto dichiarando un puntatore a puntatore, o un array di puntatori, facendolo puntare a locazioni allocate dinamicamente o preallocate.
Proviamo quest'ultimo in modo che sia allocato in stack e non in heap:

Hello : aliased constant String := "Hello"
World : aliased constant String := "World!"

Greet : array (1..2) of access constant String := (Hello'Access,World'Access);

Ecco quello che volevo, ma l'attributo 'Access non è il massimo in fatto di efficienza e performance, e di sicuro non è il massimo dell'eleganza. Ed io vado matto per il codice elegante! L'unica soluzione è la memoria dinamica:

Greet : array (1..2) of access constant String := (new String'("Hello"), new String'("World"));

Ora ci siamo, ma sono rimasto sorpreso di una cosa, guardando il segmento text il codice è allocato in stack! Com'è possibile? Girando forum di Adaisti e poi approfondendo sul reference manual di Ada (avrò cercato su più di 500 pagine, sob): se l'operatore new è usato su un tipo access-to-constant e la dimensione è conosciuta in compile time, l'elemento viene allocato in stack.
La notizia è favolosa! Questa caratteristica permette parecchie ottimizzazioni, oggi che l'operatore new viene usato e straabusato. Credevo che lo slogan Ada is a self-optimizing language fosse solo una trovata pubblicitaria, mi ero sbagliato.

Sempre più contento della scelta di Ada.


martedì 22 settembre 2009

Oberon, questo sconosciuto

Niklaus Wirth, pioniere del software engineering e designer dei linguaggi Pascal, Modula e Modula-2, sviluppò il linguaggio Oberon con gli stessi obiettivi di Ada, un linguaggio safe e con strong-typing che potesse permettere di rilevare i più comuni errori di programmazione in compile-time. Dall'anno di rilascio (1986) si può presupporre che Wirth abbia lanciato una sfida al linguaggio Ada, dato che in quel periodo era molto in voga (soprattutto fra i language designer). Wirth inoltre sosteneva che Ada fosse troppo grosso come linguaggio, ed in effetti lo è. Ovviamente non mancò la risposta di Ichbiah (designer di Ada), riporto l'intera discussione tradotta qui:

Niklaus Wirth:
"It throws too many things at the programmer, I don't think you can just learn a third of Ada and be fine. There are places where you tread on one of these spots which you haven't learned about, and it backfires on you."
"Offre troppe cose al programmatore, non penso che tu possa imparare un terzo di Ada ed essere apposto. Ci sono posti in cui calpesterai uno di questi punti che non avrai imparato, e questi ti si ritorceranno contro.

Jean Ichbiah:
"There are times when Wirth believes in small solutions for big problems. I don't believe in that sort of miracle. Big problems need big solutions!"
"Ci sono volte in cui Wirth crede in piccole soluzioni per problemi grandi. Non credo in questo tipo di miracolo. Grandi problemi richiedono grandi soluzioni!"

Da gran sostenitore di Ada devo ammettere che Wirth ha ragione. I programmatori, secondo Wirth, quando sviluppano non devono pensare al linguaggio, ma al programma che stanno sviluppando o si ritroverebbero ad affrontare anche i problemi del linguaggio (per i difetti di design questa è pratica comune in C/C++). In questo caso però Wirth mette in risalto la vastità e complessità del linguaggio, dietro Ada ci sono troppi concetti che al programmatore possono sfuggire sia durante lo studio del linguaggio sia durante la stesura di un programma.
Wirth nel design del linguaggio Oberon stavolta ha dato il meglio di sé in fatto di semplicità, il linguaggio Oberon può essere espresso in sole 33 regole grammaticali e il report (una sorta di reference manual) è di sole 20 pagine. Nonostante la semplicità del linguaggio, Oberon può offrire:
- Modularity, concetto di privato e pubblico
- Metodi
- Abstract data
- Type extension (Inheritance)
- Handler e message passing
Queste tecniche elencate fanno parte dell'Object Orientation, in una forma totalmente semplificata. Sviluppare un compilatore Oberon è infatti molto semplice, ma non solo. La semplicità del linguaggio permette compilazioni velocissime e anche un'ottima generazione di codice asm. A tal proposito OberonOS (sempre sviluppato da Wirth e co. in Oberon) è un sistema operativo molto minimale che, oltre al compilatore, comprende anche stack tcp/ip ed un browser web, tutto in un floppy da 1.44mb. Questo os è stato la base per uno più vasto e complesso per l'utilizzo in ambienti embedded, col nome di Bluebottle. Quest'ultimo contiene layer più astratti, supporto SMP, supporto per la decodifica formati multimediali, ecc, in modo da essere utilizzato come webserver, mediacenter tv, ed altro. Il sistema base di Bluebottle occupa solo 30mb.

Oberon ha suscitato il mio interesse, ma non posso dire che mi piace come linguaggio per un motivo:
  1. Qualunque programmatore può leggere un programma scritto in Ada, perché quest'ultimo favorisce la lettura di un codice senza necessariamente conoscere il linguaggio (sviluppare in Ada, invece, richiede conoscenze). Ada è bello da leggere.
    Invece Oberon non è di lettura immediata, bisogna prima conoscere il linguaggio anche se lo studio può durare solo qualche minuto. Oberon è brutto da leggere. Tuttavia..
  2. Imparare Ada è difficile, questo può richiedere molto tempo e soprattutto molta padronanza, per sviluppare un programma devi prima progettarlo e dichiararne le specifiche.
    Imparare Oberon è facile, basta leggersi le 20 pagine del report e puoi fin da subito leggere e scrivere codice mantenendo una buona modularità.
Un'altra cosa che potrebbe essere banale per molti ma importante per me, sta nel fatto che Oberon ha le reserved word tutte in upcase (maiuscolo), dando l'effetto di un codice scritto con un linguaggio molto vecchio.
Comunque è un linguaggio su cui Wirth sta dedicando gli ultimi sforzi, addirittura nella sua pagina pubblica ancora report aggiornati nonostante si sia ritirato nel 1999 (l'ultimo è Oberon-7, nel 2008) e "Trouble Spots" ovvero punti non ben chiari sul design di Oberon (sono pochi). Un simile documento venne pubblicato sotto lo stesso nome per il linguaggio Algol con molte pagine.

giovedì 17 settembre 2009

Bug o non bug?

Nel periodo di luglio il kernel di Linux è stato affetto da un bug che ha causato parecchi dibattiti nei forum e nelle mailing-list, ciò mi ha portato a volerne sapere di più, anche se dopo è risultato parecchio banale.
Il problema è il seguente:

static unsigned int tun_chr_poll(struct file *file, poll_table *wait)
{
struct sock *sk = tun->sk; // initialize sk with tun->sk
...
if (!tun) return POLLERR; // if tun is NULL return error
...

Il kernel era compilato con l'ottimizzazione per rimuovere i check sui NULL quando, dalla situazione, è ovvio che non lo sia. Il compilatore gcc, però, sopravvaluta un pò troppo i programmatori C. La dereferenzazione (l'accesso alla variabile a cui punta il puntatore) per il compilatore è indice che il puntatore non è NULL, con la conseguenza che tutti i check sui NULL vengono rimossi dalla funzione. In questo modo la funzione continua a lavorare su un puntatore NULL in cui un attacker potrebbe mappare il proprio codice malevole. Ma lasciando perdere la sicurezza, se questa funzione venisse eseguita senza che il device tun esista, il kernel andrebbe in crash.
Da qui il dibattito, problema del codice o dell'ottimizzazione del compilatore? Di entrambi, ma la colpa maggiore è soprattutto del programmatore!

Il NULL pointer dereferencing è una delle vulnerabilità più conosciute, prima di fare qualsiasi tipo di dereferencing bisogna sempre controllare se il puntatore non sia NULL. Mi chiedo come molti utenti possano dare la colpa solo al compilatore per questa vulnerabilità, chi non è programmatore non dovrebbe esprimere giudizi in merito. Mi chiedo anche in che mondo era la testa di Torvalds quando ha revisionato una patch del genere (e non è il solo a revisionare patch). Non voglio biasimarlo con questo articolo, è vero che il kernel di Linux ha fatto passi da gigante in questi ultimi anni con la versione 2.6 ma la stabilità non è la stessa che si era raggiunta con il 2.4. Con la quantità enorme di patch che arrivano sicuramente non avranno il tempo di revisionarle con attenzione, probabilmente molte nemmeno le leggono. In questo senso FreeBSD ha uno sviluppo migliore, le patch vengono sempre revisionate da 3 o 4 persone e passa sempre molto tempo prima che possano essere applicate con i consensi di tutti.

Non poteva mancare il commento su Ada.
Inutile dire che in Ada un problema del genere non può esistere, perché in Ada95 se una funzione ha come parametro un access type (puntatore) questo non può essere null. Quindi i programmatori devono sempre fare un check sui null prima di mandare un puntatore ad una funzione, questo limita parecchio i problemi di sicurezza. Con Ada non si potranno risolvere tutti i problemi che affliggono i software, ma una buona parte si.


Nota: Prima ho fatto riferimento ad Ada95 perché nello standard Ada2005 è stata rimossa l'impossibilità di passare valori null ai parametri delle funzioni, questo (purtroppo) per interfacciarsi meglio ad altri linguaggi che non hanno questo limite. Tuttavia rimane sempre possibile restringere questo caso specificando il puntatore come not null access. In questo modo il comportamento sarà uguale al precedente standard, e in più sarà visibile al programmatore quando una funzione esplicitamente non richiede un puntatore di tipo null.

mercoledì 16 settembre 2009

Parallelismo nei linguaggi

Il tempo passa e il nuovo standard C++ è alle porte, attualmente è chiamato C++0x, in cui la 'x' indicherà l'anno di rilascio dello standard ISO. Le novità sono tantissime, ma raccoglie da entrambi i lati pareri positivi e negativi, le critiche non mancano mai.
Non voglio parlare del C++ ma di una caratteristica che è stata proposta, un tasking model nel linguaggio. Non è ancora definito se il nuovo standard C++ renderà disponibile ai programmatori una libreria dedicata al tasking/threading o sarà integrata nel linguaggio stesso, caratteristica che Ada ha sin dal suo primo standard rilasciato nell'83.
Da qui i pareri contrastanti, chi ritiene che una cosa del genere debba essere lasciata lato libreria, chi invece, per questioni di correttezza e coerenza del codice, debba essere una caratteristica del linguaggio. Inoltre pare che un tasking model nel linguaggio stesso possa offrire delle ottimizzazioni.In questo post su comp.lang.ada è stato fatto un benchmark tra C++98 con i posix thread (uso diretto) e il tasking model di Ada. Nonostante il tasking model di Ada sia più di alto livello rispetto alle chiamate dirette, ha ottenuto un risultato da 2 a 3 volte più veloce rispetto ai posix thread!
Per curiosità ho voluto provare anche io, facendo solo due test:

Prima volta:
(Ada)
Executed 500000 iterations

real 0m0.935s
user 0m0.514s
sys 0m0.923s

(C++98)
Executed 500000 iterations

real 0m2.878s
user 0m0.591s
sys 0m2.751s

Seconda volta:
(Ada)
Executed 500000 iterations

real 0m1.064s
user 0m0.524s
sys 0m1.023s

(C++98)
Executed 500000 iterations

real 0m3.316s
user 0m0.742s
sys 0m3.441s

Chiudendo un occhio ed accettando statisticamente queste due misure, posso ben dire che il risultato è di circa 3 volte più veloce. E non solo, il tasking model di Ada è safe, quindi non è error prone (soggetto ad errori).
Se da sempre sono stati scelti linguaggi con un parallelismo incluso nel linguaggio per il parallel computing, sicuramente ci sarà stato un buon motivo.

lunedì 14 settembre 2009

Safe and secure software

Scrivo a poca distanza dal primo post per portare all'attenzione un libro molto interessante, rilasciato pubblicamente da John Barnes, Safe and Secure Software (An invitation to Ada 2005).
John Barnes è uno degli autori della prima revisione di Ada nel 1983, insieme a Robert Firth, Mike Woodger e infine Jean Ichbiah che ne diresse il progetto. Oggi la manutenzione del linguaggio è diretto da Tucker Taft che insieme ad un team correggono continuamente lo standard da ambiguità e possibili errori, è infatti previsto uno standard revisionato della versione 2005, che si chiamerà Ada 2005r2.
Tornando al soggetto dell'articolo, Barnes in questo libro mette in risalto le caratteristiche Safe e Secure di Ada, entrambi questi termini in italiano significano "sicuro", ma in inglese hanno un significato diverso, sopratutto nel campo del software engineering.
Il termine secure sta ad indicare che l'uomo non deve compromettere il software (sicuramente è una cosa che già sapevate). Il termine safe sta ad indicare, invece, che il software non deve compromettere l'uomo. Questo termine è meno conosciuto perché più utilizzato in ambienti dove il software è ritenuto mission-critical, come per esempio negli aeromobili, nelle apparecchiature mediche e tutto ciò in cui un errore del software può portare gravi danni ad una o più persone. In questa introduzione (non è un manuale), Barnes mette in risalto le soluzioni tipiche dei linguaggi unsafe e il corrispondente in Ada totalmente safe senza troppi sforzi da parte del programmatore.
Con l'occasione faccio notare che Barnes è autore di diversi libri su Ada, tra questi i più famosi: Programming in Ada 2005 e High Integrity Software: The SPARK approach.

domenica 13 settembre 2009

Un blog su Ada

Questo non è il solito blog in cui una persona parla di se, di eventi o problemi sociali, o qualunque altra cosa voglia condividere con altri. Non sono il tipo, ma penso che se c'è qualcosa da condividere, deve essere nuovo o poco conosciuto, infatti il linguaggio Ada e in generale la programmazione per scopi mission critical sono argomenti poco trattati qui in Italia.
Gli argomenti che verranno trattati in questo blog saranno puramente informativi per quei pochi appassionati (se ci sono in Italia) a questo linguaggio, non è mio intento scatenare i soliti flame Ada vs C++ vs Java (ce ne sono già parecchi su usenet) anche se qualche riferimento è d'obbligo, non è nemmeno mio intento far cambiare linguaggio "per la vita" a molti che sicuramente sviluppano per professione, lo è invece stuzzicare l'interesse di quei programmatori "hobbisti" che cercano lo state of the art nella programmazione.
Programmare in Ada non è solo un modo migliore di programmare, ma è anche conoscere i segreti che vi sono dietro un linguaggio, portando spesso (i più curiosi) alla teoria dei compilatori.

Difficile riassumere tutte le caratteristiche di Ada in poche parole, ma proverò a farlo specificandone sopratutto gli obiettivi per cui il dipartimento di difesa americano ne chiese lo sviluppo.

  • Ada è un linguaggio fondato sul software engineering. Anche se le prime bozze del linguaggio cominciarono dal lontano '78, Ada (e non ADA, non è un acronimo) è un linguaggio che forza la modularizzazione ed offre tutte le caratteristiche dell'Object Orientation. Il software engineering nacque come una serie di tecniche, inizialmente non incluse nei linguaggi, per programmare "bene" e permettere il lavoro in team (vedi Code Complete di McConnel). Queste tecniche solo in un secondo momento divennero parte dei linguaggi di programmazione. Ada fu uno dei primi e nacque con queste tecniche già incluse (nell'83), offrendo per primo la programmazione generica.
  • Ada è un linguaggio safe. Il type system è di tipo strong, supporta il range constraint (limitare un tipo di dati ad un range di numeri) ed evita i dangling pointer. Questo permette di limitare (o addirittura evitare) i buffer overflow.
  • Ada è un linguaggio wide-spectrum. Ada permette di operare sia a basso livello che ad alto livello. Supporta packed struct, machine code insertion e tanto altro (queste feature nel linguaggio C non fanno parte dello standard), nel contempo può supportare un garbage collector e vari runtime check. Queste caratteristiche rendono Ada un vero linguaggio general purpose, portando il suo uso sia in ambito kernel che in progetti mission critical in cui la stabilità è una priorità assoluta. Riguardo i kernel, giusto per citare un esempio: MarteOS è un OS scritto interamente in Ada per sistemi embedded.
  • Ada è un linguaggio rigoroso. Per lo standard 2005, l'ARM (Ada Reference Manual) è di 791 pagine, mentre l'AARM (Annotated ARM, per chi sviluppa compilatori) è di 1086 pagine. Vi è anche un protocollo per l'accesso alla struttura semantica e sintattica del codice, chiamato ASIS (Ada Semantic Interface Specification), questo semplifica parecchio la vita ai tool di analisi statica del codice alla ricerca di bug, UML modeller, RAD e tanto altro. Ada con le dovute restrizioni può diventare un linguaggio formale, e da qui nasce un subset di Ada chiamato SPARK; il codice di un linguaggio formale, come quest'ultimo, può essere verificato per controllarne la correttezza, tutto questo a scapito dell'uso di istruzioni non verificabili come goto, memoria dinamica, ed altre caratteristiche. (Nei sistemi embedded non viene utilizzata la memoria dinamica). I tool di SPARK verificano la correttezza del codice e fanno parte degli analizzatori di codice statico, il codice eseguibile viene sempre generato da un compilatore Ada.
Ci sarebbe tanto altro da dire su Ada, una cosa che però merita attenzione è che non si tratta di un linguaggio morto. La General Dynamics UK questo settembre (2009) ha scelto SPARK per il progetto di un elicottero della marina, qui i dettagli. Tra i software engineer rimane solo un linguaggio adatto per scopi mission critical, e questo ne limita il suo uso anche in altri ambiti. Ada è un linguaggio molto vasto, io stesso non lo conosco ancora tutto e scopro sempre qualcosa di nuovo, sorprendendomi sempre di più per la sua versatilità. Lo scopo di questo blog è anche condividere le mie "scoperte", oltre a mettere alla luce la presenza di questo linguaggio nel mondo informatico.

Al prossimo articolo