Das Böse Büro

Le etichette sono il contrario del pensiero.

Computer comunisti?

Come sapete, per me l'informatica non e' solo un lavoro, ma una vera passione. E quindi mi interessa anche l'archeologia e la storia. Detto questo, un amico che ama molto speculare mi ha chiesto se ci siano caratteristiche tecniche “capitaliste” nei computer che usiamo, e se quindi possano esistere dei computer ad architettura “comunista”, cioe' computer la cui architettura sia legata alle esigenze o ai paradigmi di un sistema comunista. La cosa divertente e' che per rispondere a questa domanda non occorre speculare, perche' effettivamente gli anni diciamo “creativi” delle architetture informatiche vedevano i sovietici fare cose, diciamo, aliene, e facilmente riconducibili all'esigenza di pianificazione e di giustificazione della spesa, di un sistema comunista.

Il problema di base e' che in un sistema sovietico l'economia era completamente pianificata. Era un periodo nel quale i computer (sia in URSS che in occidente) li facevano le universita', in occidente magari con la cooperazione di un'azienda tipo IBM, e quindi erano vastamente finanziati dallo stato. Di conseguenza, si faceva ogni cinque anni una pianificazione e si decideva cosa finanziare, e specialmente CHI finanziare: essendo che l'intero sistema teoricamente doveva servire chi lavorava, avreste ottenuto macchine per fisici, per ingegneri, per militari, per controllo industriale.

In Occidente ha vinto storicamente più o meno questa successione: von Neumann → CPU general purpose → byte → registri → C → Unix → RISC/x86 → enormi quantità di software sopra.

In alcuni filoni sovietici invece troviamo continuamente tentativi di dire:

“Perché devo fare una macchina generica? Qual è la struttura matematica del problema? Costruiamo direttamente quella, fatta per quelli che ci lavoreranno.”

Da lì vengono:

  • ternario perché matematicamente ha proprietà interessanti;
  • stack perché le espressioni sono alberi;
  • tagged memory perché i valori hanno un tipo;
  • hardware per linguaggi ad alto livello;
  • SIMD perché molti problemi scientifici sono vettori;
  • macchine specializzate per algebra simbolica;
  • parallelismo esplicito invece di fingere che tutto sia una sequenza di istruzioni.

Ed è proprio questo che le rende “aliene”: non partono necessariamente dalla CPU come oggetto fondamentale. Partono dal calcolo che vuoi fare e cercano una macchina che gli assomigli.

Se volete andare ancora più nel weird, ce n'è un secondo strato decisamente più oscuro: M-9 di Kartsev, macchine di Glushkov, sistemi macro-pipeline, REFAL, SKIF, Kronos, le architetture recursive/tagged di Akademgorodok.

Lì cominciano veramente le cose da “la storia del computing avrebbe potuto andare da un'altra parte”.





E ce ne sono parecchie che, viste oggi, sembrano provenire da una linea evolutiva alternativa del calcolo. Non tutto era esclusivamente sovietico — spesso c'erano idee parallele anche in Occidente — ma in URSS alcune furono portate molto più avanti o assemblate in modi davvero strani.

Quelle che secondo me meritano di più sono queste:

  1. Setun / Setun-70 — calcolo ternario bilanciato Non 0,1, ma −1, 0, +1. Quindi un numero può essere rappresentato senza bit di segno separato e la negazione è banalmente lo scambio + ↔ −. Inoltre in ternario alcune operazioni aritmetiche hanno meno riporti rispetto al binario. Il primo Setun fu costruito alla Moscow State University nel 1958 e ne vennero prodotti circa 50. La cosa veramente aliena però è Setun-70. Non era semplicemente “Setun più veloce”: era una macchina nuova, organizzata intorno a tryte di 6 trit, istruzioni a lunghezza variabile e una architettura a due stack. Il programma veniva espresso sostanzialmente in una forma di notazione polacca inversa e l'architettura era pensata per rendere naturali le strutture del programma. Quindi qui abbiamo già qualcosa di concettualmente folle: non una CPU che “esegue istruzioni”, nel senso moderno, ma una macchina progettata intorno all'algebra delle espressioni e alla struttura del programma.
  2. MIR — il computer che capiva matematica La serie MIR di Viktor Glushkov, dalla metà degli anni Sessanta, era progettata apposta per ingegneri e matematici. Non era semplicemente una macchina con BASIC o FORTRAN sopra: parte del suo linguaggio ad alto livello era supportata direttamente dall'architettura.
    MIR poteva lavorare simbolicamente su frazioni, polinomi, derivate e integrali.
  3. In altre parole, l'idea non era:

    CPU generica → sistema operativo → programma di algebra.

    ma qualcosa più simile a:

    computer = macchina matematica interattiva.

    È una genealogia abbastanza diversa da quella PC/Unix. Se Mathematica avesse avuto dei nonni hardware, MIR sarebbe uno di quelli.

  4. Elbrus-1 / Elbrus-2 — il tipo dei dati è proprietà della memoria Questa è una delle mie preferite. Ogni parola di memoria aveva un tag hardware che diceva che cosa quella parola era. Il processore quindi non vedeva soltanto 48/64 bit da interpretare secondo l'istruzione corrente: aveva informazione hardware sul tipo dell'oggetto. Conseguenza: molte cose che oggi affidiamo al compilatore, al runtime o alla sicurezza della memoria erano controllate dalla macchina. E ancora più stranamente, su Elbrus non esisteva realmente l'assembler nel senso tradizionale. Sistema operativo, compilatori e software di sistema venivano scritti in EL-76, un linguaggio ad alto livello imparentato concettualmente con ALGOL 68. Va detto che qui i sovietici non partirono dal nulla: l'influenza delle macchine Burroughs B5000/B6500 è importante. Ma svilupparono l'idea in una direzione propria. Vista nel 2026, la cosa fa pensare immediatamente a tagged memory, capability architectures, CHERI, managed runtimes e memory safety.

  5. Elbrus e le procedure come oggetti hardware Collegato al punto precedente: la CPU era progettata sapendo che esistevano procedure, stack, tipi e strutture del linguaggio. Il modello dominante che abbiamo ereditato è:

    CPU stupida, compilatore intelligente.

    La filosofia Elbrus/Burroughs era molto più:

    CPU che conosce la semantica fondamentale del linguaggio.

    Quindi chiamate di procedura, stack e protezione non erano convenzioni inventate dal compilatore sopra un ISA di registri: entravano direttamente nell'architettura. È probabilmente uno dei più grandi “what if?” della storia dei computer.

  6. PS-2000 — 64 processori che fanno la stessa cosa su dati diversi, nel 1981
    Qui comincia a sembrare familiare per una ragione inquietante.
    PS-2000 aveva fino a 64 processing elements, ciascuno con memoria locale, governati da una unità comune che inviava loro le operazioni. Era una macchina SIMD. Vale a dire:

    stessa istruzione → moltissimi elementi di dati contemporaneamente.

    Ti ricorda qualcosa? Una GPU. Non era una GPU naturalmente, ma la filosofia è proprio quella. E non era un giocattolo sperimentale: ne furono prodotti 242 esemplari, usati fra l'altro per elaborazione sismica e telemetria spaziale in tempo reale. Il motivo era molto sovietico: avevano enormi flussi di dati geofisici, radar, telemetrici e scientifici, quindi invece di chiedersi “come facciamo una CPU general purpose più veloce?” si chiedevano:

    come facciamo a masticare 64 flussi dello stesso tipo contemporaneamente?

  7. M-10 — parallelismo su istruzioni E dati negli anni Settanta Mikhail Kartsev progettò M-10 per il sistema sovietico di early warning antimissile. Era una macchina multiprocessore, vector-parallel, progettata per sfruttare parallelismo sia a livello delle istruzioni sia dei dati. Nel 1972. Aveva circa 5 MB di memoria e dell'ordine di 5 milioni di operazioni al secondo; per certi modelli fisici poteva essere enormemente più veloce di BESM-6. E anche qui il contesto cambia completamente l'architettura. Non:

    computer general purpose che occasionalmente fa calcolo parallelo.

    ma:

    oggetto matematico parallelo al quale attacco un problema fisico.

  8. BESM-6 — una CPU stranissima anche se binaria BESM-6 vale la pena guardarlo proprio perché corregge l'equivoco iniziale: era binario, ma comunque molto poco “PC-like”. Parole da 48 bit, memoria word-addressed, due pipeline separate per controllo e aritmetica, piccoli buffer associativi per istruzioni, operandi e risultati, look-ahead di diverse istruzioni. Nel paper sull'hardware viene descritta una ALU toroidale, costruita intorno a nuclei magnetici e logiche abbastanza diverse dalla struttura che immaginiamo pensando a una CPU moderna. Non è concettualmente aliena quanto Setun, ma è un ottimo esempio del fatto che la microarchitettura non aveva ancora prodotto la nostra grammatica attuale.

  9. DSSP — il software diventa continuazione della CPU ternaria Quando Setun-70 sparì come macchina fisica, la sua architettura sopravvisse sotto forma di DSSP, Dialogue System of Structured Programming. Ed è una cosa filosoficamente interessante: invece di portare un linguaggio su una nuova CPU, fecero in pratica il contrario, emulando il modello astratto della vecchia macchina sulle macchine binarie successive. Cioè l'ISA smette quasi di essere hardware e diventa un modello computazionale persistente. È molto vicino all'idea moderna di VM, bytecode e abstract machine.




E fin qui siamo ancora al bizzarro. Ma col comunismo, nella job description arriva qualcosa di ancora piu' bizzarro. Il mondo sovietico si proponeva una “scienza sovietica” che doveva essere non solo piu' avanzata, ma completamente diversa, al punto da diventare identitaria. E con essa, una tecnologia sovietica. E questo, unito al “cosmismo”, una strana filosofia iperpositivista scientificista che in confronto Elon Musk e' uno che leggeva superman e ci credeva, portava i sovietici a finanziare progetti “perche' questa e' scienza sovietica e voi occidentali non potete capire”.



Insomma, il sistema comunista consentiva che venissero finanziate cose infinanziabili in occidente. I financial controller avrebbero sbattuto la testa sul tavolo gridando “pecche? pecche?”, e la riunione di risks assessment sarebbe finita in una sessione di taranta sotto LSD. Ma loro potevano.

E uscirono robe davvero aliene, possibili solo in sistemi che non badano a “successo sul mercato” ma a “idea interessante e superiore a quelle occidentali”.


Qui entriamo nella parte davvero interessante: non più semplicemente “computer sovietici strani”, ma vere e proprie famiglie di idee che provavano a ridefinire che cosa fosse una macchina.

Prima però una correzione rispetto alla carrellata precedente: avevo mescolato scuole ed epoche diverse. SKIF non appartiene davvero all'archeologia sovietica classica: è un progetto russo-bielorusso nato dopo la dissoluzione dell'URSS, all'inizio degli anni 2000. Lo terrei quindi come discendente tardivo di certe idee, non come membro della stessa famiglia storica.

Il nucleo veramente alieno passa soprattutto attraverso Kartsev, Glushkov, Turchin e Akademgorodok.

M-9 di Kartsev: l'operando non è un numero, è una funzione

M-9 è probabilmente uno dei progetti più strani dell'intera storia dell'informatica sovietica.

Mikhail Kartsev lo propose nella seconda metà degli anni Sessanta per problemi nei quali occorreva elaborare enormi quantità di dati, in particolare segnali radar.

L'idea fondamentale era questa:

Per certi problemi scientifici non ha molto senso considerare l'operando elementare come un numero. L'operando naturale può essere direttamente una funzione.

Immaginiamo, per esempio, una funzione di due variabili:

f(x,y)

campionata su una griglia 32 x 32.

Su una macchina convenzionale penseremmo qualcosa del genere:

for x
    for y
        risultato[x,y] = operazione(f[x,y])

Kartsev invece dice, in sostanza:

No. f è l'operando.

E progetta una matrice di 32 x 32 unità aritmetiche, quindi 1024 elementi di calcolo capaci di lavorare contemporaneamente sui punti della funzione.

Una rappresentazione molto semplificata potrebbe essere:

                    FUNZIONE F(x,y)
                           |
                           v
        +--------------------------------------+
        |                                      |
        |        32 x 32 PROCESSING ELEMENTS   |
        |                                      |
        |   PE  PE  PE  PE  PE  ...  PE       |
        |   PE  PE  PE  PE  PE  ...  PE       |
        |   PE  PE  PE  PE  PE  ...  PE       |
        |   .   .   .   .   .        .        |
        |   .   .   .   .   .        .        |
        |   PE  PE  PE  PE  PE  ...  PE       |
        |                                      |
        +--------------------------------------+
                           |
                           v
                    FUNZIONE G(x,y)

La semantica della macchina diventa concettualmente qualcosa del tipo:

G := sin(F)

H := F + G

K := derivata_x(H)

e non:

LOAD elemento 17
ADD  elemento 18
STORE elemento 19

È una differenza enorme.

Non stai semplicemente costruendo una CPU molto veloce.

Stai costruendo una macchina nella quale l'oggetto matematico del problema coincide quasi con l'oggetto hardware della computazione.

Il progetto M-9 prevedeva inoltre componenti specializzati differenti, destinati a classi diverse di operazioni:

              +----------------------+
              |  PROCESSORE CONTROLLO |
              +----------+-----------+
                         |
       +-----------------+------------------+
       |                 |                  |
       v                 v                  v

+-------------+    +-------------+    +-------------+
| FUNCTIONAL  |    | NUMERICAL   |    | ASSOCIATIVE |
| OPERATOR    |    | COMPUTER    |    | COMPUTER    |
+-------------+    +-------------+    +-------------+

                         |
                         v

                  +-------------+
                  | PERIPHERAL  |
                  | COMPUTER    |
                  +-------------+

L'obiettivo teorico era arrivare nell'ordine del miliardo di operazioni al secondo, in un'epoca nella quale macchine come BESM-6 stavano nell'ordine del milione.

Il progetto completo non entrò in produzione, ma diverse idee confluirono poi nell'M-10.

Oggi ciò che gli assomiglia di più non è una CPU tradizionale.

È una specie di incrocio fra:

  • GPU;
  • array processor;
  • SIMD;
  • tensor accelerator;
  • domain-specific architecture.

E siamo intorno al 1967.

M-9 e il parallelismo esplicito

Nel progetto M-9 compare anche un'altra idea interessante.

La macchina prevedeva operazioni relativamente semplici, ma parole di comando molto larghe capaci di specificare contemporaneamente attività su più unità funzionali.

Una visione semplificata potrebbe essere:

              UNA PAROLA DI COMANDO
                       |
        +--------------+--------------+
        |              |              |
        v              v              v

   [ operazione ] [ operazione ] [ operazione ]
        |              |              |
        v              v              v

      UNIT A          UNIT B         UNIT C

Il parallelismo non deve essere scoperto faticosamente dalla CPU osservando una sequenza di istruzioni.

È già dichiarato nella struttura del comando.

Retrospettivamente, alcuni storici hanno paragonato certi aspetti di questa impostazione a concetti che in seguito avremmo chiamato RISC e VLIW.

Sarebbe naturalmente anacronistico dire che Kartsev “inventò RISC” o “inventò VLIW”.

Ma la domanda è sorprendentemente moderna:

Perché costruire una ISA estremamente complicata, se posso usare operazioni semplici e rendere esplicito il parallelismo?

Questo è un tratto ricorrente in molti dei progetti sovietici più interessanti:

prima viene il problema matematico, poi si costruisce l'architettura che gli assomiglia.

Glushkov: aboliamo proprio la macchina di von Neumann

Viktor Glushkov andò ancora oltre.

Negli anni Settanta sviluppò l'idea di un recursive computer, una macchina pensata esplicitamente per superare alcuni limiti fondamentali dell'architettura von Neumann.

Una macchina tradizionale viene concettualmente vista così:

                PROGRAMMA
                    |
                    v
          SEQUENZA DI ISTRUZIONI
                    |
                    v
                 +-----+
                 | CPU |
                 +--+--+
                    |
                    |
              +-----+-----+
              |           |
              v           v
          MEMORIA       I/O

Il programma viene trasformato in una sequenza:

istruzione 1
     |
     v
istruzione 2
     |
     v
istruzione 3
     |
     v
istruzione 4
     |
     v
    ...

Ma molte espressioni matematiche reali non hanno affatto questa forma.

Assomigliano molto più a un albero:

                    F
                  /   \
                 /     \
                G       H
               / \     / \
              A   B   C   D

Allora nasce una domanda ovvia, ma estremamente radicale:

Perché trasformare artificialmente questa struttura in una sequenza lineare di LOAD, STORE, CALL e JUMP?

La recursive machine cercava di fare della struttura ricorsiva del problema la struttura stessa della computazione.

Invece di:

LOAD A
LOAD B
CALL G
STORE X
LOAD C
LOAD D
CALL H
STORE Y
LOAD X
LOAD Y
CALL F

l'idea diventa qualcosa di molto più vicino a:

                    [ F ]
                   /     \
                  /       \
              [ G ]       [ H ]
              /   \       /   \
             A     B     C     D

e la macchina cerca di valutare direttamente quella struttura.

La versione completa di questa architettura non fu mai realizzata nella forma immaginata da Glushkov.

La tecnologia dell'epoca semplicemente non rendeva economico costruire tutto ciò che serviva.

Ma parecchie idee confluirono in progetti più concreti.

Macro-pipeline: pipeline di algoritmi, non di istruzioni

Il termine “pipeline” può trarre in inganno.

Una CPU convenzionale può avere una pipeline del tipo:

FETCH
  |
  v
DECODE
  |
  v
EXECUTE
  |
  v
MEMORY
  |
  v
WRITEBACK

Ogni stadio manipola singole istruzioni.

Nel macro-pipeline sviluppato dalla scuola di Glushkov, invece, ogni stadio può rappresentare un pezzo consistente dell'algoritmo.

Immaginiamo di avere molti oggetti:

X1  X2  X3  X4  X5  X6  ...

e una computazione composta da:

A(X) -> B(X) -> C(X) -> D(X)

Possiamo assegnare:

CPU1 = A
CPU2 = B
CPU3 = C
CPU4 = D

e ottenere una situazione del genere:

tempo --->

             CPU1        CPU2        CPU3        CPU4
              A           B           C           D

X1         [ A ]

X2         [ A ] ------> [ B ]

X3         [ A ] ------> [ B ] -----> [ C ]

X4         [ A ] ------> [ B ] -----> [ C ] -----> [ D ]

X5         [ A ] ------> [ B ] -----> [ C ] -----> [ D ]

X6         [ A ] ------> [ B ] -----> [ C ] -----> [ D ]

Fin qui può sembrare una normale pipeline.

Ma la differenza fondamentale è che A, B, C e D non sono istruzioni.

Possono essere interi processi numerici.

La granularità è macroscopica.

È come se invece di pipelinizzare:

ADD
MUL
LOAD
STORE

pipelinizzassi:

filtra il segnale
        |
        v
calcola la trasformata
        |
        v
riconosci il pattern
        |
        v
aggiorna il modello

Il macro-pipeline dinamico

La parte più interessante è che la pipeline non doveva necessariamente essere fissa.

Le architetture sviluppate attorno a queste idee prevedevano processori con memoria distribuita e interconnessioni riconfigurabili.

Un processore poteva ricevere un lavoro quando i dati necessari diventavano disponibili.

Quindi la struttura dell'esecuzione poteva emergere dal grafo delle dipendenze, invece di essere imposta da una sequenza rigida di istruzioni.

Per esempio:

             +--------+
        A -->| TASK 1 |----+
             +--------+    |
                           v
                        +--------+
                        | TASK 3 |----> RISULTATO
                        +--------+
                           ^
             +--------+    |
        B -->| TASK 2 |----+
             +--------+

TASK 3 non parte perché il program counter è arrivato alla sua istruzione.

Parte quando i suoi input sono pronti.

Vista oggi, questa idea ricorda immediatamente:

  • task graph;
  • DAG scheduler;
  • dataflow runtime;
  • actor systems;
  • futures;
  • stream processing;
  • workflow engines distribuiti.

Da questa linea di ricerca nacquero macchine concrete come ES-2701 ed ES-1766, prodotte negli anni Ottanta.

Erano sistemi multiprocessore MIMD, con riconfigurazione dinamica e l'obiettivo di ottenere una crescita quasi lineare delle prestazioni aggiungendo risorse.

Quindi non si trattava soltanto di teoria.

Le costruirono davvero.

REFAL: Lisp da una linea evolutiva differente

Con REFAL, sviluppato da Valentin Turchin, usciamo dall'hardware ed entriamo nei linguaggi.

REFAL nasce negli anni Sessanta come linguaggio orientato alla manipolazione simbolica.

Ma non pensa esattamente come Lisp.

Lisp tende a vedere strutture costruite attraverso liste e operazioni come:

CAR
CDR
CONS

REFAL parte invece dall'idea che un programma sia fondamentalmente un insieme di trasformazioni di forme simboliche.

Il cuore è:

PATTERN MATCHING
       +
TERM REWRITING

Concettualmente puoi immaginare regole del tipo:

f(0)  -> 1

f(n)  -> n * f(n-1)

La computazione diventa allora una sequenza di trasformazioni:

            f(3)
              |
              v
         3 * f(2)
              |
              v
      3 * 2 * f(1)
              |
              v
   3 * 2 * 1 * f(0)
              |
              v
              6

Non stai più necessariamente “eseguendo istruzioni”.

Stai riscrivendo un oggetto simbolico finché assume una forma finale.

Un'altra rappresentazione può essere:

+------------------+
| FORMA SIMBOLICA  |
+--------+---------+
         |
         | trova pattern
         v
+------------------+
| APPLICA REGOLA   |
+--------+---------+
         |
         v
+------------------+
| NUOVA FORMA      |
+--------+---------+
         |
         | trova pattern
         v
        ...

Quanto è moderno REFAL

Questa impostazione compare oggi dappertutto.

Nei compilatori:

AST pattern
     |
     v
AST trasformato

Nei sistemi di algebra simbolica:

x + 0  -> x

x * 1  -> x

Nei theorem prover:

termine
   |
   v
termine normalizzato

Negli ottimizzatori:

pattern IR
    |
    v
IR migliore

REFAL nasce esattamente per questo genere di manipolazione.

Turchin sviluppò poi anche il concetto di supercompilation.

L'idea è molto più ambiziosa dell'ottimizzazione locale.

Invece di guardare una singola istruzione o una piccola funzione, il sistema cerca di esplorare simbolicamente il comportamento del programma e costruirne una nuova versione specializzata.

Molto schematicamente:

               PROGRAMMA
                   |
                   v
        ESECUZIONE SIMBOLICA
                   |
                   v
        ANALISI DELLE FORME
                   |
                   v
       SPECIALIZZAZIONE CODICE
                   |
                   v
          NUOVO PROGRAMMA

In termini moderni, la supercompilation ha parentela concettuale con:

  • partial evaluation;
  • symbolic execution;
  • program specialization;
  • compile-time reasoning;
  • aggressive optimizer transformations.

E queste idee vengono sviluppate già negli anni Settanta.

Programmi come reti dinamiche di automi

Un'altra linea collegata alla scuola di Glushkov sviluppò architetture nelle quali il programma poteva essere rappresentato come una rete di automi.

Un automa poteva rappresentare:

  • un dato;
  • un'operazione;
  • una relazione;
  • un riferimento;
  • una risorsa hardware.

Gli automi potevano combinarsi in strutture più grandi.

A quel punto la macchina non deve necessariamente seguire un program counter:

PC = PC + 1

La computazione può consistere nella trasformazione di una rete:

       RETE 0
          |
          v
       RETE 1
          |
          v
       RETE 2
          |
          v
       RETE 3
          |
          v
      RISULTATO

Oppure:

       [ A ]----\
                 \
                  >----[ F ]----\
                 /               \
       [ B ]----/                 \
                                   >----[ H ]
       [ C ]----\                 /
                 \               /
                  >----[ G ]----/
                 /
       [ D ]----/

La computazione non consiste necessariamente nel percorrere la rete da sinistra a destra.

La rete stessa può cambiare struttura durante l'esecuzione.

È un modo radicalmente diverso di intendere il computer.

Assomiglia più a una combinazione di:

  • graph reduction;
  • dataflow;
  • term rewriting;
  • Petri nets;
  • reti di automi;

che a una CPU tradizionale.

Data-driven computing

Una delle possibilità era il calcolo data-driven.

L'operazione parte quando sono disponibili i dati necessari.

Per esempio:

        A --------\
                   \
                    +----> [ ADD ] ----> C
                   /
        B --------/

ADD non viene eseguita perché:

PC = indirizzo_di_ADD

Viene eseguita perché:

A disponibile = SI
B disponibile = SI

quindi:

             A pronto
                \
                 \
                  >---- [ ADD ] ----> C
                 /
                /
             B pronto

Non serve necessariamente un program counter globale che ordini:

adesso esegui ADD

È la disponibilità dei dati a far partire il calcolo.

Demand-driven computing

L'altra possibilità è quasi speculare.

Qualcuno richiede C.

La macchina scopre che per calcolare C servono A e B.

Quindi:

                 WANT C
                   |
                   v
        +-----------------------+
        | Per produrre C servono|
        |         A e B         |
        +-----------+-----------+
                    |
             +------+------+
             |             |
             v             v
          WANT A         WANT B
             |             |
             +------+------+
                    |
                    v
                [ CALCOLO ]
                    |
                    v
                    C

La computazione viene quindi attivata dalla domanda del risultato.

È concettualmente molto vicina alla lazy evaluation.

Solo che qui la lazy evaluation non è soltanto una proprietà del linguaggio.

Può diventare una proprietà dell'architettura.

Von Neumann contro dataflow

La differenza filosofica si vede abbastanza bene così.

Macchina convenzionale:

           PROGRAM COUNTER
                 |
                 v
          istruzione 1
                 |
                 v
          istruzione 2
                 |
                 v
          istruzione 3
                 |
                 v
          istruzione 4

La domanda fondamentale è:

"Qual è la prossima istruzione?"

In una macchina dataflow:

        A ----\
               >---- [ OP1 ] ----\
        B ----/                   \
                                    >---- [ OP3 ] ---> RESULT
        C -------- [ OP2 ] -------/

la domanda diventa:

"Quale operazione possiede già tutti gli input necessari?"

È una differenza molto più profonda di quanto sembri.

Akademgorodok e Kronos

Spostiamoci a Novosibirsk, nell'ambiente scientifico di Akademgorodok.

Qui nasce Kronos.

La genealogia è interessante perché Kronos non compare dal nulla.

Il punto di partenza è la workstation Lilith di Niklaus Wirth, progettata a ETH Zürich per Modula-2.

Il gruppo sovietico ricevette documentazione su Lilith e Modula-2 nei primi anni Ottanta e iniziò a sviluppare il proprio sistema.

Ma invece di limitarsi a costruire una workstation compatibile, spinse l'idea molto più avanti.

Il ragionamento era sostanzialmente:

Se il software viene scritto in Modula-2, perché costruire una CPU pensata per assembler?

Kronos diventa quindi qualcosa di molto vicino a una:

        VIRTUAL MODULA-2 MACHINE
                   |
                   v
              HARDWARE REALE

Cioè una macchina virtuale del linguaggio trasformata direttamente in processore fisico.

Kronos: il linguaggio entra nella CPU

Una CPU convenzionale conosce cose come:

MOV
ADD
PUSH
CALL
JMP

Il significato di:

modulo
procedura
variabile locale
variabile globale

viene costruito sopra la CPU dal compilatore.

Possiamo rappresentarlo così:

       PROGRAMMA MODULA-2
               |
               v
          COMPILATORE
               |
               v
     ISTRUZIONI GENERICHE
               |
               v
              CPU

Kronos cerca invece di accorciare quella distanza:

       PROGRAMMA MODULA-2
               |
               v
        CODICE KRONOS
               |
               v
      CPU CHE CONOSCE GIA'
       MOLTI DI QUEI CONCETTI

L'architettura supporta direttamente concetti come:

MODULE

PROCEDURE

LOCAL VARIABLE

GLOBAL VARIABLE

e operazioni associate a:

  • procedure call;
  • strutture di stack;
  • variabili locali;
  • variabili globali;
  • moduli;
  • range checking;
  • costrutti tipo CASE;
  • caricamento dinamico di moduli.

La semantica del linguaggio è quindi parzialmente incorporata nell'hardware.

È un ribaltamento importante.

La struttura tradizionale è:

LINGUAGGIO
    |
    v
COMPILATORE
    |
    v
ISA
    |
    v
CPU

La filosofia Kronos si avvicina molto di più a:

LINGUAGGIO
    |
    v
ISA
    |
    v
CPU

o addirittura, concettualmente:

LINGUAGGIO ~= ISA

Kronos e la stack machine

Kronos utilizza intensamente uno stack per valutare le espressioni.

Prendiamo:

a + b * c

Una macchina a registri potrebbe produrre qualcosa del genere:

LOAD R1, b
LOAD R2, c
MUL  R1, R2
LOAD R2, a
ADD  R2, R1

Su una stack machine il modello diventa:

PUSH a
PUSH b
PUSH c
MUL
ADD

e lo stack evolve così:

inizio:

    +-------+
    |       |
    +-------+

PUSH a:

    +-------+
    |   a   |
    +-------+

PUSH b:

    +-------+
    |   b   |
    +-------+
    |   a   |
    +-------+

PUSH c:

    +-------+
    |   c   |
    +-------+
    |   b   |
    +-------+
    |   a   |
    +-------+

MUL:

    +-------+
    |  b*c  |
    +-------+
    |   a   |
    +-------+

ADD:

    +-------+
    |a+b*c  |
    +-------+

Non serve fare register allocation nello stesso modo di una CPU tradizionale.

La filosofia ricorda molto ciò che più tardi avremmo visto in:

  • JVM bytecode;
  • CLR;
  • WebAssembly;
  • Forth;
  • altre stack machines.

La differenza interessante è che qui quella macchina virtuale viene materializzata direttamente nell'hardware.

Kronos e gli opcode compatti

L'architettura Kronos era estremamente compatta.

Molte operazioni venivano rappresentate con opcode da un byte.

In una CPU complessa moderna possiamo avere qualcosa del genere:

+--------+-------+------+--------------+-----------+
| opcode | modrm | sib  | displacement | immediate |
+--------+-------+------+--------------+-----------+

In una macchina di tipo Kronos il modello può essere molto più vicino a:

+--------+
| opcode |
+--------+

Naturalmente non tutte le operazioni reali si riducono magicamente a un solo byte senza dati aggiuntivi, ma la filosofia dell'ISA è molto più vicina a quella di una bytecode machine che a quella di un x86.

Ed è importante ricordare che non stiamo parlando di una VM software.

Stiamo parlando della CPU fisica.

Kronos e la protezione del software

Kronos incorporava anche concetti che rendevano naturale distinguere:

  • codice;
  • dati;
  • moduli;
  • procedure;
  • riferimenti interni ed esterni.

Quindi il mondo non appare semplicemente come:

MEMORIA = UN ENORME ARRAY DI BYTE

ma più come:

+----------------------------------+
| PROCESSO                         |
|                                  |
|   +-----------+   +-----------+  |
|   |   CODE    |   |   DATA    |  |
|   +-----------+   +-----------+  |
|                                  |
|   +---------------------------+  |
|   | MODULE TABLE              |  |
|   +---------------------------+  |
|                                  |
|   +---------------------------+  |
|   | PROCEDURE / ENTRY TABLE   |  |
|   +---------------------------+  |
+----------------------------------+

Questo rende molto naturali concetti come:

  • relocation;
  • dynamic linking;
  • reentrant code;
  • separazione fra aree;
  • riferimenti mediati da tabelle.

Non significa che Kronos fosse una moderna capability machine o che avesse automaticamente tutte le proprietà di sicurezza che oggi associamo a CHERI o ai managed runtime.

Ma la filosofia è molto diversa dalla classica:

ecco 4 GB di memoria, arrangiati

Kronos diventa MARS

Kronos finì poi dentro il progetto MARS, acronimo di Modular Asynchronous Developable Systems.

E qui Akademgorodok torna sul tema del parallelismo.

L'idea generale era costruire sistemi:

MODULARI
    +
ASINCRONI
    +
ESPANDIBILI
    +
RICONFIGURABILI

Invece di pensare:

QUESTA E' LA CPU
QUESTA E' LA MACCHINA

si pensa qualcosa di più simile a:

           +---------+
           | MODULE  |
           +----+----+
                |
        +-------+-------+
        |               |
        v               v
   +---------+      +---------+
   | MODULE  |      | MODULE  |
   +----+----+      +----+----+
        |                |
        +-------+--------+
                |
                v
           +---------+
           | MODULE  |
           +---------+

La macchina è quindi l'insieme dei moduli e delle loro relazioni.

Vista oggi, questa mentalità ricorda:

  • manycore;
  • compute tile;
  • network-on-chip;
  • transputer;
  • message passing;
  • chiplet architectures.

Naturalmente non bisogna proiettare direttamente tecnologie moderne nel passato.

Ma il tipo di domanda architetturale è sorprendentemente simile.

Recursive architectures e tagged architectures: non sono esattamente Kronos

Qui occorre distinguere bene alcune cose che avevo compresso troppo nella lista iniziale.

Le recursive architectures appartengono principalmente al filone legato a Glushkov e ad altri gruppi che lavoravano esplicitamente su architetture non-von-Neumann.

Le tagged architectures hanno invece una storia parzialmente differente.

Un'architettura tagged associa a una parola di memoria non soltanto il valore, ma anche informazione sul tipo dell'oggetto.

Una memoria tradizionale vede:

+------------------------------+
|       0101010101010101       |
+------------------------------+

La CPU deve decidere dal contesto se quei bit significano:

integer
pointer
instruction
float
character
...

In una tagged architecture l'oggetto assomiglia invece a:

+---------+------------------------------+
|   TAG   |           VALUE              |
+---------+------------------------------+

per esempio:

+---------+------------------------------+
| INTEGER |          123456              |
+---------+------------------------------+

+---------+------------------------------+
| POINTER |          0xABCD              |
+---------+------------------------------+

+---------+------------------------------+
| CODE    |           ...                |
+---------+------------------------------+

L'hardware può quindi sapere che cosa sta manipolando.

Questo tipo di idea compare con particolare forza nella famiglia Elbrus.

Kronos, invece, è meglio descritto come:

STACK MACHINE
      +
HIGH-LEVEL-LANGUAGE MACHINE
      +
MODULAR ARCHITECTURE

non semplicemente come “recursive tagged machine”.

Il punto comune: eliminare il divorzio fra problema e macchina

A questo punto si può vedere un filo comune.

Una CPU convenzionale riceve un problema del mondo reale:

PROBLEMA
   |
   v
MODELLO MATEMATICO
   |
   v
ALGORITMO
   |
   v
LINGUAGGIO
   |
   v
COMPILATORE
   |
   v
ISTRUZIONI
   |
   v
CPU

Ogni passaggio distrugge un po' della struttura originale del problema.

Alla fine una cosa che magari nasce come:

          A
        /   \
       B     C
      / \   / \
     D   E F   G

diventa:

LOAD
LOAD
ADD
STORE
LOAD
CALL
JUMP
...

Molti dei progetti sovietici più strani provano invece ad accorciare quella catena.

M-9:

FUNZIONE MATEMATICA
        |
        v
OPERANDO HARDWARE

REFAL:

ESPRESSIONE SIMBOLICA
        |
        v
OGGETTO COMPUTAZIONALE

Glushkov:

GRAFO DEL PROBLEMA
        |
        v
STRUTTURA DELLA COMPUTAZIONE

Kronos:

COSTRUTTO DEL LINGUAGGIO
        |
        v
COSTRUTTO DELLA CPU

Macro-pipeline:

FASE DELL'ALGORITMO
        |
        v
UNITA' DI ESECUZIONE PARALLELA

Questa è probabilmente la cosa veramente “aliena”.

SKIF: interessante, ma appartiene a un'altra epoca

Infine c'è SKIF.

SKIF non è propriamente sovietico.

È un programma russo-bielorusso nato dopo il 2000 e orientato alla costruzione di supercomputer cluster.

A quel punto l'hardware non ha più l'aspetto esotico delle vecchie macchine:

+---------+      +---------+      +---------+
|  NODE   |------|  NODE   |------|  NODE   |
|  Linux  |      |  Linux  |      |  Linux  |
+---------+      +---------+      +---------+
     |                |                |
     +----------------+----------------+
                      |
                  NETWORK

CPU commodity, cluster, sistemi operativi relativamente convenzionali.

Ma una parte della vecchia filosofia riappare nei sistemi software per il parallelismo.

In particolare nella T-system ritorna una domanda tipica di quella scuola:

Perché deve essere il programmatore a specificare manualmente tutta la sequenza di esecuzione, se può limitarsi a dichiarare le dipendenze?

Il modello diventa più simile a:

        TASK A ----\
                    \
                     >---- TASK C ----\
                    /                  \
        TASK B ----/                    >---- TASK E
                                       /
        TASK D -----------------------/

Il programmatore descrive il grafo.

Il runtime decide:

quali task sono pronti?

dove posso eseguirli?

quanti processori ho?

come distribuisco il lavoro?

Ed ecco l'ironia storica.

Negli anni Sessanta:

Costruiamo hardware strano per esprimere naturalmente il parallelismo.

Negli anni Duemila:

Va bene, usiamo CPU normali. Facciamo diventare strano il runtime.

Una linea evolutiva alternativa

Se mettiamo tutte queste idee in una tabella concettuale, viene fuori qualcosa di interessante:

Sistema / scuola Oggetto fondamentale della computazione
Von Neumann classico istruzione su una parola
M-9 funzione / matrice
REFAL espressione simbolica
Recursive machine struttura ricorsiva
Dynamic architecture rete di automi
Dataflow dipendenza fra dati
Macro-pipeline processo / task
Kronos costrutto di linguaggio
MARS modulo computazionale
T-system / SKIF dipendenza fra task

La linea evolutiva che ha vinto commercialmente può essere grossolanamente rappresentata così:

VON NEUMANN
     |
     v
CPU GENERAL PURPOSE
     |
     v
REGISTRI + MEMORIA
     |
     v
ASSEMBLER
     |
     v
C / FORTRAN
     |
     v
UNIX
     |
     v
RISC / x86
     |
     v
ENORMI STRATI DI SOFTWARE

Molti di questi filoni sovietici provavano invece qualcosa del genere:

        PROBLEMA
           |
           v
   STRUTTURA MATEMATICA
           |
           v
 "CHE MACCHINA SERVE PER
  RAPPRESENTARLA DIRETTAMENTE?"
           |
           v
       ARCHITETTURA

E questa è la differenza fondamentale.

La linea classica continua a chiedersi:

Come facciamo a eseguire più velocemente una sequenza di istruzioni?

Questi gruppi continuavano invece a chiedersi:

Perché stiamo rappresentando il problema come una sequenza di istruzioni?

Ed è una domanda molto più profonda.

Il ritorno delle vecchie idee

La parte divertente è che molte di queste idee, viste dal presente, non sembrano affatto morte.

Non perché ci sia necessariamente una genealogia diretta.

Spesso non c'è.

Ma davanti agli stessi problemi, l'informatica moderna ha ricominciato a inventare strutture sorprendentemente simili.

M-9:

M-9
 |
 +----> array processor
 |
 +----> GPU
 |
 +----> tensor processor

Macro-pipeline:

MACRO-PIPELINE
      |
      +----> DAG scheduler
      |
      +----> stream processing
      |
      +----> distributed workflow

REFAL:

REFAL
 |
 +----> term rewriting
 |
 +----> compiler IR transformations
 |
 +----> symbolic execution
 |
 +----> program specialization

Kronos:

KRONOS
 |
 +----> JVM
 |
 +----> CLR
 |
 +----> WebAssembly
 |
 +----> language-oriented VM

Recursive/dataflow machines:

DATAFLOW / RECURSIVE MACHINE
           |
           +----> lazy evaluation
           |
           +----> graph reduction
           |
           +----> futures
           |
           +----> task graphs

Non significa che le tecnologie moderne derivino direttamente da quelle sovietiche.

Significa qualcosa di forse ancora più interessante:

quando il problema ritorna, certe soluzioni matematiche ritornano con lui.

E probabilmente il caso più spettacolare rimane M-9.

Perché dire:

"una funzione è un operando hardware"

nel 1967 non significa semplicemente progettare una CPU veloce.

Significa avere già smesso di pensare che la CPU debba necessariamente essere una macchina che prende un numero alla volta e gli fa qualcosa.

E da lì in poi, praticamente, può succedere di tutto.




Quindi si, sono esistiti computer “comunisti” nel senso che soltanto l'organizzazione finanziaria di un sistema comunista, indifferente al problema “successo sul mercato”, poteva finanziare.

E le idee venivano da “partiamo dall'idea che gli occidentali stiano sbagliando perche' non capiscono Marx. che succede se noi....”

Ed e' uscita roba davvero aliena.



Uriel Fanelli

--
Written using Blogfrei: https://git.keinpfusch.net/loweel/blogfrei
Fedi: @uriel@bbs.keinpfusch.net
XMPP: uriel@keinpfusch.net
vecchio blog: https://blog.keinpfusch.net
email: blog@keinpfusch.net