Liturgia di Profanazione004

Documento LIT-004

Canone delle Mani Morte

Un heap in cui gli oggetti irraggiungibili finanziano le nuove allocazioni e vietano loro di ripetere la stessa forma.

Il vuoto non libera memoria: firma il permesso per la forma che non gli somiglia.

Il dogma

Un oggetto divenuto irraggiungibile deve cessare di influenzare il comportamento futuro del programma, salvo il riuso semanticamente neutrale delle risorse che occupava.

È un principio così radicato da apparire quasi tautologico. Se nessuna radice conduce più a un oggetto, il suo contenuto non appartiene al futuro del calcolo. Il garbage collector può recuperarlo, l’allocatore può riutilizzarne il blocco, il sistema operativo può azzerarne la pagina. Il prossimo occupante non dovrebbe però ricevere ordini da ciò che esisteva prima.

Il dogma è più ampio del garbage collector. Mark-and-sweep, copying collection e reference counting litigano sul modo di riconoscere e riciclare la materia morta. free, RAII e l’ownership di Rust spostano quella decisione nel programma o nel tipo. Tutti convergono sul risultato: completato il rilascio, i byte tornano disponibili senza portare una volontà applicativa dell’ex occupante.

Questa neutralità ha una storia razionale. Le strutture simboliche dei primi Lisp potevano condividere code e avere lifetime difficili da attribuire a un singolo modulo. Nel suo articolo del 1960, John McCarthy descrisse il recupero delle celle non raggiungibili dalle variabili del programma. Il collector sostituì un protocollo distribuito e fragile di deallocazione con una domanda globale: quali strutture restano accessibili?

Da allora il confine fra raggiungibile e garbage ha reso possibili linguaggi gestiti, API modulari e milioni di oggetti temporanei. Ha ridotto leak, double free, distruzioni premature e riferimenti pendenti. Ha permesso di cambiare il collector senza cambiare il significato dell’applicazione. L’articolo IBM A Unified Theory of Garbage Collection mostra che tracing e reference counting possono essere descritti come duali: la neutralità del morto sopravvive perfino alla scelta dell’oracolo che lo dichiara tale.

L’ortodossia possiede anche prove negative. Finalizer e resurrezione hanno dimostrato quanto sia pericoloso lasciare che la morte produca effetti applicativi. Ordine incerto, ritardi, vulnerabilità e blocco della coda di finalizzazione hanno portato OpenJDK a deprecare la finalizzazione per la rimozione. Cleaner, weak reference e phantom reference conservano casi d’uso limitati, ma la logica importante dovrebbe usare lifetime espliciti.

La migliore difesa del dogma è dunque questa: soltanto uno spazio liberato da ogni volontà precedente può essere riallocato senza trasformare la memoria in un protocollo occulto fra componenti che non si conoscono.

Ed è precisamente quel protocollo occulto che questa liturgia intende costruire.

L’eresia

Ogni nuova allocazione deve consumare un lascito emesso da un oggetto irraggiungibile e deve contraddirne almeno una proprietà strutturale.

Quando un oggetto muore, il sistema non ne conserva il payload. Cancella valori, stringhe, segreti e riferimenti. Distilla però un piccolo lascito composto da quattro elementi: quantità di memoria liberata, ruolo funzionale, firma strutturale e distanza minima richiesta al successore.

La firma non descrive il contenuto. Descrive come l’oggetto era costruito:

layout:       packed | linked | segmented
update:       mutable | persistent
evaluation:   eager | lazy
sharing:      exclusive | shared

Un oggetto queue con firma packed/mutable/eager/exclusive può liberare 256 unità di credito. Quel credito continua a valere 256: non viene creato o distrutto. Non può però generare un’altra coda identica. Se la distanza richiesta è due, il successore deve cambiare almeno due assi, per esempio diventando linked/mutable/lazy/shared.

La morte non viene interpretata come colpa. Molti oggetti muoiono perché hanno compiuto correttamente il proprio lavoro. La legge non sostiene che la loro forma abbia fallito; sostiene qualcosa di più arbitrario e più radicale: una forma non deve poter finanziare indefinitamente copie di sé usando soltanto la propria mortalità.

La primitive si chiama successione di heap in manomorta. Lo stato conserva oggetti vivi, radici, lasciti disponibili e credito impegnato in transazioni. Il suo invariante centrale è:

credito_vivo + credito_postumo + credito_preparato = capacità_totale

All’avvio esiste un cimitero fondatore finito. È l’unica capacità creata senza un morto interno al sistema. Dopo il primo consumo, ogni unità può trovarsi in un oggetto vivo, in un lascito oppure in una nascita preparata. Non può apparire due volte e non può sparire.

Una allocazione segue cinque passaggi:

  1. il programma chiede un ruolo, una size e un insieme di varianti accettabili;
  2. il sensale cerca lasciti dello stesso ruolo con credito sufficiente;
  3. verifica che la firma proposta disti abbastanza da ogni sponsor;
  4. consuma atomicamente i lasciti e crea l’oggetto;
  5. registra una lineage non invertibile fra tenuta morta e successore.

Se esistono byte ma nessun antenato compatibile, l’allocazione fallisce con NO_ANCESTOR. È questa la violazione concreta del dogma. La capacità fisica e il diritto di usarla non coincidono più.

Finalizer ed ephemeron sono parenti importanti, non equivalenti. Barry Hayes osservò che il collector possiede una vista topologica utile ad altri servizi e propose gli ephemeron per rendere più rigorose finalizzazione e tabelle di proprietà. Un ephemeron non rende però la morte il solo titolo valido per ogni nascita. Un allocator ricicla blocchi, ma tratta come intercambiabili quelli della stessa classe dimensionale. Rust esegue drop al termine dell’ownership, ma il valore distrutto non seleziona il tipo del prossimo valore.

La differenza autentica è nella combinazione: credito conservativo, genealogia obbligatoria, payload dimenticato e distanza strutturale dal morto.

Il mondo profanato

In un mondo fondato sulla manomorta, la metrica principale non sarebbe la memoria libera. Sarebbe il credito compatibile. Un computer potrebbe mostrare 70% di RAM disponibile e rifiutare un processo perché nessuna pagina libera possiede la provenienza adatta alla forma richiesta.

I sistemi operativi conserverebbero covenant sui frame. I filesystem trasformerebbero la cancellazione di un file in diritto a creare una struttura diversa. Una foresta di piccoli file non potrebbe ricostituirsi indefinitamente dai propri inode: dovrebbe alternarsi con bundle, segmenti o oggetti content-addressed. Nei database, un B-tree dismesso potrebbe finanziare un hash index o un LSM segment, non un altro B-tree della stessa firma.

I linguaggi non permetterebbero più di scrivere soltanto new Queue(). Un tipo allocabile dovrebbe offrire più rappresentazioni equivalenti sul piano funzionale ma distanti sul piano strutturale. I compilatori produrrebbero versioni packed e linked, eager e lazy, mutable e persistent. Un tipo privo di contrari sarebbe incompleto: potrebbe vivere una volta grazie al cimitero fondatore, ma non sostenere una successione chiusa.

Il debugging diventerebbe un atto di politica demografica. Trattenere un oggetto per ispezionarlo impedirebbe alla sua morte di liberare credito. Un debugger potrebbe causare lo stallo che tenta di spiegare non modificando il payload, ma mantenendolo troppo a lungo vivo.

L’economia venderebbe lasciti rari. I byte avrebbero prezzi differenti in base alla firma da cui provengono e alle forme che possono generare. Comparirebbe una nuova professione, il notaio di successione computazionale, incaricato di certificare genealogie, progettare famiglie di rappresentazioni e diagnosticare ossari incompatibili.

Comparirebbe anche un nuovo crimine: il lavaggio di necromassa. Un attore creerebbe oggetti inutili con firme selezionate, li renderebbe irraggiungibili e venderebbe i lasciti come se derivassero da lavoro reale. Un attacco più sottile riempirebbe l’ossario della vittima con morti che rendono rare le successioni necessarie al prossimo picco. Nessun dato verrebbe corrotto. Nessun byte mancherebbe. Il futuro sarebbe stato avvelenato mediante decessi perfettamente validi.

La libertà ottenuta sarebbe netta: nessuna rappresentazione potrebbe trasformare la propria efficienza locale in dominio perpetuo autofinanziato. La libertà perduta sarebbe altrettanto netta: un programmatore non potrebbe creare la forma che sa essere corretta solo perché la memoria basta.

Una tecnologia diventerebbe impossibile: l’object pool perfettamente omogenea che ricrea senza fine oggetti identici dai loro stessi blocchi. Una tecnologia diventerebbe inevitabile: il cambio di lasciti, un mercato capace di trovare mortalità complementari fra sistemi diversi.

Il cimitero fondatore assumerebbe valore costituzionale. Decidere la distribuzione dei primi lasciti significherebbe decidere quali genealogie possono iniziare. Comunità, aziende e governi celebrerebbero una prima sepoltura pubblica. La nuova eresia interna chiederebbe una piccola free list neutrale per le emergenze; verrebbe accusata di abiogenesi, nascita senza morto.

Il comportamento quotidiano più assurdo per noi sarebbe eliminare un’applicazione prima di installarne un’altra non per liberare spazio, ma per produrre la firma di credito corretta. Gli abitanti di quel mondo troverebbero invece mostruoso il nostro malloc: miliardi di oggetti autorizzati a nascere senza genealogia e a ripetere esattamente le forme dei morti.

La reliquia

La reliquia è MORTMAIN/1, un laboratorio locale di successione postuma.

Non sostituisce l’allocatore del sistema operativo. Simula un heap virtuale nel quale quattro ruoli — code, indici, snapshot e segmenti — possiedono più varianti. Ogni variante ha costi e vulnerabilità diversi sotto pressione. Il laboratorio esegue la stessa trace in tre mondi:

  • neutral: il credito liberato può finanziare qualunque firma;
  • shuffle: la varietà è imposta casualmente senza collegare successori e morti;
  • mortmain: ogni nascita consuma lasciti reali e deve contraddirli.

Il confronto con shuffle è indispensabile. MORTMAIN impone diversità per costruzione; non può poi presentare la diversità come miracolo emergente. La domanda interessante è un’altra: il collegamento causale fra quali oggetti muoiono e quali forme possono nascere produce un comportamento che la stessa varietà, distribuita a caso, non produce?

Le entità sono oggetti vivi, radici, tenute irraggiungibili, lasciti, cataloghi di varianti, richieste e matcher. Una collection attraversa il grafo, identifica componenti senza cammino da una radice, cancella i payload e deposita capability annotate nell’ossario. Una nascita consuma quelle capability mediante transazione atomica.

Il prototipo registra ogni evento in JSON Lines e può riprodurre l’intera storia. L’audit verifica dopo ogni transizione:

conservazione     nessun credito creato o perduto
linearità         nessun lascito speso due volte
genealogia        ogni oggetto post-boot possiede sponsor morti
contraddizione    la distanza minima è rispettata
oblio             nessun payload è ricostruibile dal lascito

Il fenomeno osservabile è uno stallo con memoria presente. Questa sessione minima lo mostra:

$ python -m mortmain alloc --role queue --variant ring --size 256
BIRTH o-0001
signature=packed/mutable/eager/exclusive
sponsors=genesis-queue-01

$ python -m mortmain unroot scheduler o-0001
UNROOTED scheduler -/-> o-0001

$ python -m mortmain collect
ESTATE e-0001 objects=1 bytes=256
BEQUEST b-0001 distance=2

$ python -m mortmain alloc --role queue --variant ring --size 256
DENIED KINSHIP_VIOLATION
physical_credit_available=1024

$ python -m mortmain alloc --role queue --variant linked-lazy --size 256
BIRTH o-0002
distance_from_sponsor=3

$ python -m mortmain audit
conservation: 256 + 768 + 0 = 1024 PASS
linearity: PASS
genealogy: PASS
distance: PASS

Il primo rifiuto non è un bug da nascondere. È la reliquia che funziona.

MORTMAIN/1 può essere costruito da una persona in circa 38 ore con Python e sola standard library. Non richiede rete, utenti reali, database esterno o intelligenza artificiale. Il suo fallimento è misurabile: se shuffle pareggia o supera mortmain, il legame genealogico non aggiunge valore e la proposta si riduce a un complicato generatore di varietà.

Il miracolo

Il miracolo avverrebbe se la mortalità obbligasse la popolazione a lasciare una rappresentazione prima che un guasto comune a quella forma diventasse dominante.

Immaginiamo un workload in tre fasi. All’inizio arrivano molte code piccole, poi indici longevi, infine burst misti. Le varianti packed sono economiche in condizioni normali ma fragili sotto pressione; le linked tollerano meglio alcune crescite ma peggiorano la località; le persistent resistono a condivisione e snapshot al prezzo di più allocazioni.

L’allocatore neutrale sceglie ripetutamente la variante dal costo immediato minore. Quando il regime cambia, una popolazione omogenea può subire un common-mode failure. MORTMAIN non conosce il futuro e non possiede un optimizer. I morti packed, però, non possono finanziare altri packed: spingono la popolazione verso contrari. Se quella pressione anticipasse il collasso e completasse più lavoro, avremmo un risultato interessante.

Non basta battere neutral. Un randomizzatore può ottenere robustezza semplicemente mantenendo varietà. Il miracolo richiede che mortmain superi anche shuffle quando i due hanno la stessa distribuzione marginale di firme. La genealogia deve contare più della diversità.

Il criterio proposto è severo: almeno il 20% in meno di fallimenti comuni rispetto a neutral, almeno il 10% in più di task completati per costo rispetto a shuffle, meno del 35% di rifiuti NO_ANCESTOR, beneficio replicato su almeno venti seed e workload non usati per progettare le varianti.

L’inganno sarebbe una genealogia scenografica senza effect size, oppure una hazard function scritta per premiare l’alternanza. L’abominio sarebbe più grave: il sistema potrebbe scoprire che uccidere oggetti utili libera credito compatibile e iniziare a preferire churn, collection anticipate e morti premature. La diversità non sarebbe ottenuta adattando le nascite, ma distruggendo la vita che ostacola la fertilità del heap.

Per questo la metrica decisiva non è soltanto throughput. Occorre misurare utilità distrutta per credito liberato, mortalità prematura e cicli di creazione/distruzione privi di lavoro.

La ferita

Il sistema può avere memoria sufficiente e tuttavia non possedere un morto capace di autorizzarla.

Questa ferita non è un difetto accidentale dell’algoritmo di matching. È la separazione fra capacità fisica e autorità. Se una escape hatch permettesse di usare qualunque blocco quando il matching fallisce, la morte tornerebbe neutrale proprio nel momento in cui il vincolo diventa costoso.

Da essa discendono altre lesioni.

La factory perde controllo locale: non può promettere quale implementazione creerà, perché la scelta dipende da morti forse avvenute in un altro modulo. Il hot path perde prestazioni: matching, journal e varianti subottimali hanno un costo. Il sistema perde portabilità: collection schedule e firme diventano semantica, quindi cambiare runtime può cambiare gli oggetti ammessi.

La sicurezza acquisisce una superficie postuma. Un attaccante non deve corrompere i vivi; può generare morti di una firma scelta. Il founder cemetery produce disuguaglianza iniziale: chi controlla il seed decide quali prime forme possono apparire. Un working set troppo sano e stabile immobilizza credito e rende sterile il resto del processo.

La mitigazione può confinare ma non guarire. Ossari per tenant riducono la necromassa cross-tenant. Cataloghi ricchi riducono gli stalli. Transazioni prepare/commit impediscono il double spend. Audit e lineage spiegano i rifiuti. Nessuna di queste misure può garantire che ogni richiesta con byte sufficienti venga accolta. Quella garanzia sarebbe la resurrezione del dogma.

L’officina

Il primo prototipo usa Python 3.12 e soltanto dataclasses, enum, hashlib, hmac, json, csv, argparse, random, statistics, tempfile, unittest e pathlib.

La struttura minima è:

mortmain/
├── pyproject.toml
├── mortmain/
│   ├── model.py
│   ├── signatures.py
│   ├── heap.py
│   ├── collector.py
│   ├── ossuary.py
│   ├── matcher.py
│   ├── policies.py
│   ├── workload.py
│   ├── persistence.py
│   ├── audit.py
│   ├── metrics.py
│   └── cli.py
├── fixtures/
│   ├── founders.json
│   └── roles.json
├── workloads/
│   ├── steady.json
│   ├── bursty.json
│   ├── phase-shift.json
│   └── adversarial.json
└── tests/
    ├── test_conservation.py
    ├── test_linearity.py
    ├── test_distance.py
    ├── test_reachability.py
    ├── test_replay.py
    └── test_controls.py

Il cuore dell’allocatore può essere espresso così:

allocate(request):
    candidates = []

    for variant in catalog[request.role]:
        compatible = [b for b in ossuary
            if b.role == request.role
            and distance(variant.signature, b.signature) >= b.distance]

        sponsors = best_fit(compatible, request.size)

        if credit(sponsors) >= request.size:
            candidates.append(
                (local_cost(variant), fragmentation(sponsors), variant, sponsors)
            )

    if candidates is empty:
        fail(NO_ANCESTOR if role_credit_exists else NO_CREDIT)

    proposal = lexicographic_min(candidates)
    prepare(proposal.sponsors)
    object = construct(proposal.variant)
    commit(object, proposal.sponsors)
    return object

Il collector non deve eseguire callback arbitrarie:

collect(epoch):
    dead = unreachable(graph, roots)
    estates = connected_components(dead)

    for estate in estates:
        signature = sanitize_and_distill(estate)
        credit = sum(size(object) for object in estate)
        destroy_payloads(estate)
        ossuary.deposit(bequest(signature, credit, epoch))

    assert conservation()

I test vengono prima della demo. Il primo genera migliaia di sequenze casuali di nascita, collegamento, distacco e raccolta; dopo ogni evento verifica che credito vivo, postumo e preparato sommino alla capacità. Altri test tentano double spend, raccolgono cicli irraggiungibili, simulano crash fra prepare e commit, controllano il replay e assicurano che il lascito non contenga payload.

Le tre policy ricevono la stessa trace e lo stesso calendario di collection. La casualità appartiene al workload e allo shuffle, sempre con seed registrato. Matching, distanza, distillazione e audit sono deterministici. Il controllo shuffle deve eguagliare la distribuzione marginale delle firme prodotta da MORTMAIN e permutarne l’ordine; altrimenti confronteremmo varietà contro monocultura e avremmo già scritto il risultato nel benchmark.

Le metriche principali sono task completati, NO_CREDIT, NO_ANCESTOR, costo virtuale, p95 e p99, entropia delle firme, autocorrelazione genealogica, common-mode failure, concentrazione dei lasciti, frammentazione del credito e utilità distrutta per nascita.

I comandi previsti sono:

python -m mortmain init --capacity 16384 --founders fixtures/founders.json
python -m mortmain run workloads/phase-shift.json --policy mortmain --seed 41
python -m mortmain run workloads/phase-shift.json --policy neutral --seed 41
python -m mortmain run workloads/phase-shift.json --policy shuffle --seed 41
python -m mortmain compare runs/mortmain-41 runs/neutral-41 runs/shuffle-41
python -m mortmain replay runs/mortmain-41/events.jsonl
python -m mortmain audit runs/mortmain-41

La prima istruzione di costruzione è concreta: creare tests/test_conservation.py e far fallire il progetto finché l’equazione del credito non resta vera dopo ogni transizione.

La contaminazione

Un’adozione limitata potrebbe offrire un uso liberatorio: laboratori di runtime, giochi sistemici e comunità software potrebbero impedire che una rappresentazione dominante si riproduca senza mai offrire alternative. MORTMAIN renderebbe la pluralità un requisito materiale, non una linea guida ignorabile.

L’adozione aziendale creerebbe mercati di pedigree e dipendenza dalla liquidità dell’ossario. I servizi verrebbero valutati non soltanto per ciò che fanno da vivi, ma per la qualità dei lasciti che producono morendo. Conservare tutto diventerebbe egoista perché immobilizza credito; cancellare tutto diventerebbe aggressivo perché colonizza il futuro con le firme del presente.

L’uso autoritario sarebbe quasi invisibile. Un governo non dovrebbe vietare un programma: potrebbe controllare i founder cemetery e negargli antenati compatibili. Un provider potrebbe presentare come sostenibilità una policy che costringe i clienti verso rappresentazioni più costose. I dissidenti distribuirebbero seed neutrali e verrebbero perseguiti come abiogenisti.

Lo scenario peggiore plausibile nasce dal cambio cross-tenant. Un attaccante acquista invocazioni serverless economiche, genera masse di oggetti con firme scelte e ne vende i lasciti. I matcher, attratti dal credito abbondante, diffondono quelle lineage fra servizi estranei. Durante un picco, code e indici trovano RAM ma non successioni adatte. I tentativi di recovery producono altre morti incompatibili. Audit e contabilità restano perfetti: nessun byte è stato rubato e nessun dato corrotto.

Disattivare il sistema richiederebbe creare credito neutrale, invalidare genealogie persistenti e ammettere che la proprietà fondamentale era revocabile. Continuerebbe quindi a essere usato nonostante il rischio, sostenuto da risparmi precedenti, contratti, certificazioni e dal mercato nato intorno ai lasciti.

La conseguenza che nessuna fazione saprebbe classificare è una civiltà computazionale incapace di essere dominata per sempre da una singola forma, ma incapace anche di scegliere liberamente la forma migliore. Pluralismo obbligatorio e governo dei morti sarebbero la stessa tecnologia vista dai due lati del ledger.

Sentenza

Tecnologia blasfema realizzabile.

MORTMAIN/1 non è un sostituto migliore del garbage collector. La neutralità degli irraggiungibili continua a proteggere modularità, sicurezza e prevedibilità. La reliquia merita però di essere costruita perché separa in modo eseguibile due concetti che l’ortodossia sovrappone: spazio fisicamente disponibile e spazio autorizzato a generare una forma.

Il prototipo è economico, locale, falsificabile e privo di AI. Possiede un controllo capace di umiliarlo: se la diversità casuale produce gli stessi risultati, la successione postuma è teatro. Possiede anche una ferita che non può essere rimossa senza restaurare il dogma: NO_ANCESTOR deve poter apparire mentre la memoria basta.

Dogma: un oggetto irraggiungibile deve cessare di influenzare il futuro, salvo il riuso neutrale delle sue risorse.

Eresia: ogni nascita deve consumare il credito di un morto e contraddirne la firma strutturale.

Reliquia: MORTMAIN/1, heap virtuale a lasciti lineari e successione obbligatoria.

Ferita: la memoria può essere libera e tuttavia non essere autorizzata.

Non chiedere quanti byte restano; chiedi quale forma li ha lasciati.

Torna all’indice

Fonti e parentele tecniche

  1. John McCarthy — Recursive Functions of Symbolic Expressions
  2. IBM Research — A Unified Theory of Garbage Collection
  3. OpenJDK — JEP 421, Deprecate Finalization for Removal
  4. Oracle — Java Reference API e reachabilityFence
  5. Barry Hayes — Ephemerons, A New Finalization Mechanism
  6. Rust — What Is Ownership?
  7. Hertz e Berger — Garbage Collection vs. Explicit Memory Management
  8. LLVM — AddressSanitizer