1 points par GN⁺ 1 일 전 | 1 commentaires | Partager sur WhatsApp
  • Lone Lisp est un Lisp qui s’exécute directement au-dessus des appels système Linux, sans libc. Matheus Moreira l’a lancé pour vérifier s’il était possible de créer un langage complet et des programmes en espace utilisateur uniquement avec du C freestanding
  • En s’appuyant directement sur l’ABI stable de l’espace utilisateur Linux, il élimine l’état global et les API héritées de la bibliothèque C, comme errno, les locales ou la mise en tampon implicite des fichiers, et implémente lui-même le runtime, l’allocateur mémoire et les outils de test
  • Il prend en charge les FEXPR et une vraie optimisation des appels terminaux, ainsi que les générateurs, les continuations délimitées et la gestion des erreurs reprenable, et traite aussi les vecteurs, tables et continuations avec la même convention d’appel de fonction
  • Le démarrage est rapide, mais dans des benchmarks provisoires, c’est un interpréteur au niveau des listes 10 à 100 fois plus lent que Python. L’interpréteur C restera comme référence de bootstrap, avec l’objectif à long terme d’implémenter un compilateur JIT dans Lone lui-même
  • Moreira utilise Claude pour les revues de code et la gestion de projet, mais écrit et relit lui-même le code de Lone. Il veut créer un générateur de site statique, un shell et des utilitaires afin de reconstruire l’espace utilisateur Linux traditionnel

Des jeux à C++, Ruby et Lisp

  • Ce qui lui a donné envie de programmer, c’est Mega Man Battle Network ; il a appris l’anglais pour échanger sur les forums associés, ainsi que son premier langage, C++
  • Dans un lycée technique au Brésil, il a commencé une formation en informatique vers 13 ou 14 ans
    • Il utilisait Dev-C++ et les tutoriels de cplusplus.com
    • En six mois, il avait assimilé tout le programme de l’école, puis a été recruté comme assistant pour aider les autres élèves
    • Il a aussi essayé de créer des jeux, sans succès faute de connaissances suffisantes en physique
  • Après C++, il est passé par Java, Ruby et Python avant de découvrir Lisp et Scheme, attiré par l’élégance linguistique de Scheme
    • Il a appris la programmation orientée objet en lisant presque entièrement les tutoriels Java de Sun
    • Il aimait Java Swing et le design Nimbus, et a même créé une application pour tracer des cercles et des arcs destinée à un professeur de mathématiques
    • En Ruby, il a écrit des gems, dont Acclaim, un parseur d’arguments orienté commandes à la manière de Git
  • L’exploration de plusieurs langages est partie de projets scolaires, mais elle a ensuite surtout été portée par la curiosité et le désir de trouver le bon langage pour lui

L’expressivité de Ruby et la transparence de C

  • En Ruby, il suffit souvent de deviner le nom d’une méthode pour tomber juste, et la prise en charge du singulier, du pluriel et des synonymes correspondait bien à sa façon de penser
    • Des compositions comme items.each.with_index.map, proches de pipelines shell, permettent d’accomplir beaucoup avec peu de code
    • Il garde encore Ruby installé en permanence et a récemment commencé un projet Rails
  • En C, les fonctions correspondent directement à des symboles ELF et à du code assembleur, ce qui rend le résultat de compilation facile à comprendre
    • Il estime que le C++ moderne est en pratique devenu un autre langage, et ne dit plus qu’il connaît C++
    • Il a essayé de passer à Rust sans y rester, et s’intéresse aussi à Zig et à son créateur, qui justifient la réécriture en C par la vitesse et l’ABI
  • Adolescent, il lisait le code source de la VM CRuby pour répondre, par l’implémentation, aux questions de Stack Overflow du type « pourquoi Ruby se comporte-t-il ainsi ? »
  • Il a ensuite exploré le code de CRuby, CPython, la JVM, Guile et V8, et considère en particulier le code source du noyau Linux comme une référence de programmation C professionnelle

Sa manière de lire le code des autres

  • Il ne se contentait pas d’utiliser des bibliothèques : il lisait régulièrement le source pour comprendre l’implémentation interne, la communication entre pilotes et matériel, et les couches cachées
  • Même en construisant de vraies applications, il se laissait souvent entraîner vers l’exploration des technologies sous-jacentes, et a aussi développé une tendance à vouloir refaire mieux des problèmes déjà résolus
  • Jugeant que cette inclinaison ne correspondait pas à une carrière classique en développement logiciel, il a choisi de ne pas faire de la programmation son métier et de la garder comme hobby
  • Il n’a pas compris le code des pilotes dès le départ ; il a continué à lire malgré la frustration, jusqu’à pouvoir saisir une partie de leur fonctionnement

Rétro-ingénierie d’un clavier RGB

  • L’application Windows de contrôle du clavier RGB d’un ordinateur portable barebone Clevo était si lente qu’elle mettait plus d’une minute à ouvrir une fenêtre, il l’a donc remplacée lui-même
  • Avec Wireshark, il a capturé les paquets USB envoyés par l’application au clavier, en a analysé la structure et a écrit un pilote C en espace utilisateur Linux
  • Après l’avoir publié sur GitHub, il l’a oublié, mais de vrais utilisateurs sont apparus, et l’un d’eux a même créé une interface graphique
  • Il a ensuite rétro-conçu d’autres fonctions du portable, comme la gestion d’alimentation ACPI, sans toutefois tout élucider

liblinux, en contournant libc

  • Les appels système Linux sont suffisamment simples pour ressembler à des appels de fonction ordinaires, et il est possible de cibler Linux directement sans passer par libc
  • Linux est connu pour la stabilité de son ABI en espace utilisateur, mais il considère que les composants de l’espace utilisateur ne le sont pas
    • Il a vu dans les manuels que certains appels système n’étaient pas pris en charge par glibc
    • Un cas rapporté par LWN, où getrandom a mis environ deux ans à entrer dans glibc, l’a aussi influencé
  • Il a développé liblinux à partir de l’idée de créer un liblinux.a ne contenant que des appels système, à lier avec -llinux
    • C’était un ensemble de wrappers d’appels système Linux pour C freestanding
    • Le runtime C, ou CRT, désigne les bibliothèques C standard comme glibc, musl ou uclibc

Les problèmes qui disparaissent en C freestanding

  • Linux renvoie les erreurs comme des valeurs de retour ordinaires, alors que la bibliothèque C les stocke dans la variable globale errno
  • En supprimant libc, on évite aussi plusieurs états globaux et vieilles API
    • errno, rand/srand, l’état des locales
    • strtok, non réentrant
    • La mise en tampon implicite des fichiers, qui peut provoquer des problèmes inattendus
    • gets, atexit, signal, malloc
    • math.h, qui se complexifie lorsqu’il se combine avec NaN et errno
  • En revanche, les règles de promotion des entiers et l’absence de vérification des dépassements restent des problèmes propres à C
    • Il utilise des fonctionnalités intégrées du compilateur pour les dépassements
    • Il reste aussi attentif au risque de créer lui-même de nouveaux défauts
  • Sans bibliothèque standard, il fallait tout écrire soi-même, de la copie d’octets à la conversion entier→chaîne, mais la tâche n’était pas aussi impossible qu’attendu
    • Le dépôt glibc était difficile à interpréter, mais le source de musl libc, propre et facile à parcourir, l’a beaucoup aidé
    • Dans Lone, il a aussi implémenté une suite de tests automatisée n’utilisant que de la mémoire statique, sans bibliothèque C ni allocation mémoire dynamique

La découverte de nolibc et les débuts de Lone Lisp

  • Il a demandé à Greg Kroah-Hartman pourquoi Linux n’avait pas de bibliothèque C officielle, et celui-ci lui a répondu qu’il avait existé autrefois un projet klibc
  • Il a ensuite découvert que les développeurs du noyau Linux avaient créé nolibc.h pour leurs outils internes
    • Le trouvant plus complet et plus pratique que son propre liblinux, il a archivé son projet
    • Aujourd’hui, nolibc.h s’est étendu dans l’arbre du noyau en un répertoire proche d’une libc temporaire, mais ses garanties de stabilité externe ne sont pas claires
  • Il a lancé ensuite Lone Lisp
    • S’il pouvait créer un Lisp freestanding exécuté directement sur Linux, alors n’importe quel programme pourrait être écrit de la même façon
    • C’est une expérience visant à retirer la complexité de l’espace utilisateur Linux moderne et à repartir d’une interface binaire stable d’appels système
  • Il a aussi envoyé des patchs à Cosmopolitan, notamment pour l’implémentation du parsing des nombres à virgule flottante
    • Il apprécie le format APE, interprété simultanément comme un exécutable de plusieurs plateformes, ainsi que la fonctionnalité d’archive embarquée dans l’exécutable

Gestion des erreurs et continuations délimitées

  • Au début, tous les chemins d’erreur de Lone terminaient dans l’interpréteur par exit(255), mais il a implémenté un système de conditions reprenables de la famille Common Lisp
  • La base de la gestion des erreurs est constituée de continuations délimitées
    • Il comprend les continuations comme une copie de frames de pile rejouée à un autre endroit
    • Il voit aussi les exceptions comme une forme de continuation qui ne capture pas de valeur
  • Les types d’erreur sont associés depuis des symboles comme 'not-found vers des lambda
    • (lambda (error) ...), avec un seul argument, se comporte comme un gestionnaire d’exception classique
    • (lambda (error continuation) ...), avec deux arguments, peut appeler la continuation pour reprendre le code qui a échoué avec une valeur
  • Il n’a volontairement pas adopté une API de restart où le code qui signale l’erreur énumère les options de redémarrage, comme use-value en Common Lisp
    • signal, throw et transfer sont construits comme des mécanismes fondamentaux internes à l’évaluateur, de sorte qu’ils se comportent comme s’ils renvoyaient simplement une valeur

FEXPR et éléments de langage appelables

  • Il évite aussi le modèle où l’évaluateur Lisp traditionnel traite directement des formes spéciales comme if
  • Lone prend en charge les FEXPR, équivalents à des plugins de l’évaluateur, pour sortir les formes spéciales de l’évaluateur
    • Cette conception a d’abord permis l’optimisation des appels terminaux pour les fonctions Lisp pures, mais les primitives ont longtemps eu un bug
    • Il l’a résolu en ajoutant un mécanisme séparé permettant aux primitives de retourner depuis une position terminale
  • Les vecteurs et les tables sont aussi appelables
    • ([1 2 3] 0) renvoie 1
    • ([1 2 3] 0 4) modifie le vecteur en [4 2 3]
  • Les continuations et les générateurs suivent la même convention d’appel
    • control ressemble à begin avec prise en charge des continuations
    • transfer sort du bloc control le plus proche et renvoie une continuation appelable représentant le point de suspension
    • Appeler cette continuation avec une valeur permet de reprendre le calcul suspendu avec une autre valeur de retour

Appels système et structure d’exécution

  • Avant le démarrage de Lone, le processus parent appelle execve, puis Lone effectue environ 12 appels mmap pour sa gestion mémoire interne
  • Il lit un script depuis l’entrée standard, l’exécute puis se termine ; pendant l’exécution, des appels mmap, munmap et mremap supplémentaires peuvent se produire
  • Le système de modules utilise openat et close
  • Parmi les plus de 300 appels système de Linux, Lone n’en utilise directement qu’une partie, mais il fournit au niveau Lisp une primitive system-call permettant aux programmes d’appeler les autres
  • Pour les appels comme ioctl, qui nécessitent des structures C du noyau, il faut ajouter des capacités de création et de manipulation de structures
    • La récupération de la taille du terminal est un exemple nécessitant ce type de prise en charge

L’espace utilisateur qu’il veut créer avec Lone

  • Au niveau du langage, il a implémenté la gestion des erreurs, une vraie optimisation des appels terminaux, les générateurs et les continuations délimitées, et veut à long terme intégrer aussi des idées d’Erlang/BEAM
  • La structure est préparée pour permettre à plusieurs interpréteurs d’exécuter du code Lisp en parallèle
  • Le premier programme qu’il veut créer avec Lone remplacera l’actuel générateur de site statique basé sur un fork de PugJS qu’il utilise
  • Il prévoit ensuite d’écrire son propre shell et ses utilitaires afin de recréer à sa manière l’espace utilisateur Linux traditionnel
  • Il a aussi créé une implémentation qui exporte env comme table Lone Lisp ; à l’époque, il n’y avait pas de parcours, mais les générateurs sont désormais pris en charge
  • L’interpréteur peut également lire et exécuter du code Lisp situé dans un segment ELF spécial
    • En copiant le binaire de l’interpréteur et en y insérant du code Lisp, on peut créer un programme autonome

Revues et gestion de projet avec Claude

  • Il n’y a pas de limite à la quantité totale de travail, mais il estime que l’ensemble de fonctionnalités du langage lui-même approche de l’achèvement
  • Il utilise Claude comme une sorte de gestionnaire, avec une aide particulièrement importante en revue de code et gestion de projet
    • Il lui demande d’examiner toute la base de code pour trouver strictement les problèmes, les zones non développées et les orientations futures
    • Il gère les approches rejetées et les fonctionnalités à traiter plus tard via des documents et une mémoire de projet
    • Le ramasse-miettes et l’allocateur mémoire ont été nettement améliorés grâce aux meilleurs algorithmes indiqués par Claude
  • Il relit lui-même chaque ligne qui entre dans Lone et, considérant que l’écriture de code est en soi éducative, il ne fait pas confiance à l’IA pour écrire le code de Lone à sa place
  • En revanche, pour son générateur de site statique JavaScript, il a déjà laissé Claude écrire une fonctionnalité entière
  • Parmi les fonctionnalités prévues figurent les API des compilateurs GCC/Clang et la prise en charge des fonctions de protection de pile

Une syntaxe inspirée de Ruby et Scheme

  • Il a cherché à combiner l’expressivité de Ruby et la concision de Scheme ; comme le parseur Lisp est simple, il a pu l’écrire lui-même puis manipuler directement des listes imbriquées
  • Il a vraiment compris Lisp lorsqu’il a implémenté les FEXPR, où une fonction reçoit la liste elle-même plutôt que des valeurs évaluées
  • Les tableaux utilisent la syntaxe [1 2 3] et les dictionnaires {k v}
    • Elle est familière non seulement aux utilisateurs de Clojure et newLisp, mais aussi à ceux de Ruby, Python et JavaScript, ce qui réduit les barrières d’entrée inutiles
  • Son style de C est basé sur une version modifiée du style du noyau Linux, avec une préférence pour davantage d’alignement et d’ornementation
  • En Ruby, il préfère réduire la ponctuation et écrire plus près d’une phrase anglaise, comme method 'arg' plutôt que method('arg')
  • En Lisp aussi, il réduit l’imbrication syntaxique
    • Le let de Lone a la forme (let (a b c d ...) body...)
    • Il juge cette forme, où noms et valeurs apparaissent dans l’ordre, plus naturelle pour l’application de fonctions que le traditionnel (let ((a b) (c d) ...) body...)

Performances et plan de bootstrap

  • Le benchmark où Lone se distingue est la vitesse de démarrage : il peut lancer et terminer un programme plus vite que Ruby ne lit Rubygems
  • Dans des mesures provisoires, il est environ 10 à 100 fois plus lent que Python
    • Lone est un interpréteur au niveau des listes, proche de la forme du chapitre 5.4 de SICP
    • Python et Ruby sont des interpréteurs de bytecode, donc dans une autre catégorie de performance
  • Il évite la compilation en bytecode, car transformer les listes en bytecode lui donne l’impression de perdre l’essence de Lisp
  • À long terme, l’interpréteur C restera comme référence finale de bootstrap, et il prévoit d’écrire un compilateur JIT à l’intérieur de Lone
    • Le principe serait de réserver des pages exécutables avec mmap, puis d’y générer des instructions
  • Dans une comparaison provisoire de Fibonacci récursif avec Bash, Lone était beaucoup plus rapide, mais il reconnaît des biais de mesure possibles, comme le fait que Bash ait peut-être créé un processus à chaque itération
  • Pour les scripts, le temps de démarrage est important, et le bootstrap rapide de Lone est donc utile
    • En précalculant les tables de hachage à l’étape de build pour en faire des données d’initialisation C, il serait possible de réduire encore le coût d’initialisation à l’exécution

Mémoire et environnement minimal

  • mmap demande des pages mémoire au noyau, et la taille de page courante est de 4 096 octets
  • On peut préciser les permissions de lecture, écriture et exécution, et un JIT utilise des pages exécutables
  • Même si l’on n’a besoin que de 16 octets, demander 4 Kio entraîne beaucoup de gaspillage ; les allocateurs ordinaires demandent donc une grande zone, par exemple 128 Mo, puis la découpent en petits morceaux
  • L’environnement de plus haut niveau de Lone ne contient que deux symboles, import et export
    • À part les listes, les littéraux et le mécanisme d’appel de fonction, tout, jusqu’à if et quote, doit être importé
    • C’est un choix de conception qui donne à l’utilisateur le contrôle direct de tous les noms de l’environnement
  • Il y a actuellement environ 50 fonctions, principalement if, let, begin, control/transfer, intercept/signal, flatten, etc.

Bibliothèque standard et documentation

  • La documentation du langage est encore incomplète, mais le travail sur la bibliothèque standard se rapproche davantage que les fonctionnalités du langage
  • Il prévoit de s’inspirer de la bibliothèque standard de Ruby et des conventions de nommage de Scheme
  • Le prochain chantier est le protocole d’itération
    • Il envisage une forme du type (for (each collection) (lambda (item) ...))
    • Les fonctions each existantes seront toutes repensées pour retourner des générateurs
  • Il veut ensuite implémenter la manipulation de chaînes et un string builder pour des concaténations efficaces
  • Une fois l’itération et les fonctions de chaîne terminées, il pense pouvoir écrire aussi un framework web de type Hiccup
  • Il craint de perdre en concentration lorsque les fonctionnalités bas niveau seront terminées et que le travail passera à une bibliothèque standard plus répétitive, et compte utiliser l’aide de Claude pour la gestion

L’objectif de posséder toute la stack

  • Même des fonctionnalités qui semblent mystérieuses, comme les continuations, deviennent des problèmes d’implémentation ordinaires lorsqu’on les comprend comme une copie de frames de pile ; Lone vise aussi à inciter d’autres personnes à créer et explorer leurs propres systèmes
  • Il veut ne pas rester simple consommateur de bibliothèques, mais devenir quelqu’un qui crée des bibliothèques, et utiliser directement des appels système souvent traités comme interdits aux développeurs ordinaires
  • Avoir son propre système implique de le réparer soi-même lorsqu’il casse, mais il espère que davantage de personnes choisiront cette responsabilité et ses récompenses
  • Il pense qu’il ne faut pas avoir peur du vieux code accumulé dans l’informatique, et qu’il est possible de supprimer des couches inutiles
    • La reconstruction de l’espace utilisateur Linux pourrait venir non pas de lui, mais de développeurs Rust ou Zig
    • Même si Lone ne réussit pas, il serait satisfait si cette tentative en inspirait d’autres

Logiciel libre et Android

  • Il accorde de l’importance au renforcement du pouvoir des utilisateurs recherché par le logiciel libre, et s’inquiète de voir les ordinateurs polyvalents réduits à des appareils ménagers gérés par des entreprises ou des gouvernements
  • Il considère systemd comme un exemple réussi de réimagination et de reconstruction de l’espace utilisateur Linux traditionnel, même s’il a parfois adopté une approche lourde
  • Il estime qu’il faut choisir ses combats plutôt que tout construire à chaque couche
    • Si Lone avait été conçu comme un Lisp bare metal, il n’aurait peut-être jamais atteint l’étape du boot
    • Il cite en exemple Sector Lisp de jart, un Lisp en secteur de démarrage de moins de 512 octets
  • Lone a commencé dans Termux et une grande partie a été écrite sur un téléphone
  • Android bloque certains appels système, dont statx, mais Lone s’exécute sur Android et sa suite de tests passe à 100 %

Médecin et programmeur non professionnel

  • Moreira est médecin, diplômé de médecine en 2019
    • Au lycée, la lecture sur la programmation de réseaux de neurones l’a amené à s’intéresser à la biologie
    • Son emploi du temps, avec certains jours de 10 à 14 heures de consultations et d’autres jours libres, lui permet de dégager du temps pour programmer
  • Dans les hôpitaux utilisant un dossier médical électronique, ses modèles et sa capacité de frappe raccourcissent beaucoup son travail
    • Il peut saisir les notes tout en gardant le contact visuel avec le patient
    • Le projet Rails qu’il a récemment commencé est un système de dossier patient électronique destiné à répondre à ses propres besoins
  • L’une des raisons pour lesquelles les développeurs professionnels ne résolvent pas directement leurs problèmes personnels serait, selon lui, un manque d’intérêt pour l’ordinateur lui-même
  • Il faut de la curiosité, plutôt qu’une simple volonté d’obtenir un résultat, pour creuser l’intérieur des implémentations ; il ne sait pas encore comment susciter cette curiosité chez les gens

1 commentaires

 
GN⁺ 1 일 전
Avis sur Lobste.rs
  • J’aime cette série d’interviews au point de les lire toutes sans exception, et j’apprécie beaucoup de découvrir la relation personnelle que chacun entretient avec l’informatique

    • Il est particulièrement intéressant de rassembler et de comparer les éléments déclencheurs qui ont amené ces personnes à chérir les ordinateurs comme quelque chose de plus qu’un simple outil
      Shonumi, l’auteur de l’émulateur GBA GBE+, a lui aussi été influencé, comme moi, par les jeux Mega Man Battle Network, et j’aimerais lire un jour son interview
  • Les exceptions redémarrables n’ont pas nécessairement besoin de continuations de première classe (first-class continuations)
    Common Lisp fournit des exceptions redémarrables sans continuations, et l’essentiel est de ne pas dérouler la pile avant d’appeler le gestionnaire
    Ainsi, la pile et l’état au moment où l’exception s’est produite sont conservés tels quels, ce qui permet au gestionnaire de retourner normalement ou, comme en Common Lisp, de sauter vers un gestionnaire de redémarrage quelque part dans la pile

    • Si j’ai donné l’impression que c’était indispensable, c’est parce que mon enthousiasme autour de cette prise de conscience était excessif
      Cela dit, dans Lone Lisp, c’est réellement nécessaire, car la continuation renvoyée peut être appelée plusieurs fois et sortir du gestionnaire de signal
      (import (lone print set quote lambda intercept signal))
      
      (set continuation  
        (intercept  
          (('return (lambda (value continuation) continuation)))  
          (signal 'return 'value)))
      
      (print (continuation 1)); 1  
      (print (continuation 2)); 2  
      (print (continuation 3)); 3  
      
      Les générateurs étant la base du traitement itératif, les performances y sont plus importantes ; j’ai donc utilisé des piles séparées plutôt que des continuations délimitées afin d’éviter les copies
      La gestion des erreurs étant un chemin relativement rare, j’ai choisi d’en fournir pleinement les fonctionnalités
  • Pour beaucoup de gens, le travail ou le projet le plus important est peut-être quelque chose qu’ils font gratuitement et par plaisir sur leur temps libre
    J’imagine que mon médecin pourrait lui aussi mener une double vie en créant en secret son propre langage Lisp

    • Il s’avère que je n’étais pas le seul
      Le Dr Cameron Kaiser a lui aussi mené d’importants projets, et il partage même sa philosophie sur la propriété des machines tout en utilisant au quotidien une station de travail POWER9 entièrement ouverte
  • Je viens de découvrir Lone Lisp, et le projet a l’air excellent
    Moi aussi, vers 13 ou 14 ans, j’ai commencé à apprendre le C++ en cours d’informatique avec le vieux Dev-C++, et y repenser après si longtemps me rend nostalgique

    • Je suis surpris que Dev-C++ soit encore en vie et qu’il y ait même eu une release en 2021
  • Je ne m’attendais absolument pas à ce qu’il soit médecin, et cela me fait penser à Con Kolivas
    Il semble qu’il y ait finalement pas mal de médecins développeurs de ce genre
    https://en.wikipedia.org/wiki/Con_Kolivas