4 points par GN⁺ 2023-10-09 | 1 commentaires | Partager sur WhatsApp
  • Berry est un langage de script à typage dynamique destiné aux appareils embarqués peu puissants ; sur une base ARM Cortex M4, le cœur de l’interpréteur fait moins de 40 KiB et peut fonctionner avec moins de 4 KiB de heap
  • Le runtime se compose d’un compilateur en un seul passage et d’une VM à registres ; l’ensemble du code est écrit en ANSI C99, ce qui facilite son intégration à du code embarqué existant
  • Pour les performances, int, real, boolean et string ne sont pas implémentés comme objets de classe, tandis que seuls list, map et range sont traités comme objets de classe
  • Les fonctionnalités du langage couvrent à la fois la programmation impérative, orientée objet et fonctionnelle, avec GC, FFI, gestion des exceptions, fichiers de bytecode et modules d’extension
  • La configuration d’objets à la compilation et la fixation en flash permettent de réduire l’utilisation de la RAM pour le code, les classes et les modules, ce qui le rend adapté aux environnements à fortes contraintes mémoire

Un runtime pensé pour l’embarqué

  • Berry est un langage de script à typage dynamique conçu pour des appareils embarqués peu performants, comme les microprocesseurs
    • Avec un CPU ARM Cortex M4, l’ISA Thumb et le compilateur ARMCC, la taille du code du cœur de l’interpréteur est inférieure à 40 KiB
    • Dans les mêmes conditions, il peut s’exécuter avec moins de 4 KiB de heap
  • L’interpréteur est écrit en ANSI C99 et comprend un compilateur de bytecode en un seul passage ainsi qu’une machine virtuelle basée sur des registres
  • La conception des types privilégie les performances d’exécution en distinguant types valeur et types objet
    • Types qui ne sont pas des objets de classe : int, real, boolean, string
    • Types qui sont des objets de classe : list, map, range
  • La configuration d’objets à la compilation stocke la plupart des objets constants dans le segment de données de code en lecture seule, ce qui réduit l’utilisation de la RAM au démarrage de l’interpréteur

Fonctionnalités du langage et modèle d’exécution

  • Les types de base incluent nil, boolean, integer, real, string, class, instance, module, list, map, range
  • Les expressions et opérateurs prennent en charge l’affectation, les relations, la logique, l’arithmétique, les opérations bit à bit, les champs, les index, la concaténation de chaînes, l’opérateur conditionnel, les parenthèses et les bytes buffers
  • Les structures de contrôle fournissent if elif else end, while, for, break, continue
  • Les fonctionnalités de fonction incluent variables locales et portée de bloc, instruction de retour, définition de fonctions imbriquées, closures basées sur des upvalues, fonctions anonymes et expressions lambda
  • Les classes ne prennent en charge que l’héritage simple public et fournissent surcharge de méthodes et d’opérateurs, méthodes constructeur et méthodes destructeur
  • Le GC utilise un Mark-Sweep GC
  • La gestion des exceptions permet de lancer n’importe quelle valeur d’exception avec l’instruction raise et prend en charge plusieurs modes de catch
  • Grâce aux fichiers de bytecode, il est possible d’exporter une fonction dans un fichier de bytecode, puis de la recharger pour l’exécuter

Modules, intégration C et exemples

  • La gestion des modules prend en charge à la fois les modules intégrés et les modules d’extension
    • Les modules d’extension incluent les modules script, les modules de fichiers bytecode et les modules de bibliothèques partagées comme *.so et *.dll
    • Le code, les classes et les modules peuvent être fixés en flash pour réduire l’utilisation de la RAM
    • Un support Regex optionnel et un mapping LVGL optionnel sont proposés
  • Grâce à la Native C interface, il peut être embarqué comme une bibliothèque dans du code existant ; du code comme Tasmota est cité en exemple
    • Un easy mapping optionnel est fourni pour appeler du code C depuis Berry
  • Les exemples de syntaxe montrent l’itération sur une liste, la création d’une map et l’itération sur ses clés, la déclaration class et l’héritage simple via l’opérateur :
  • D’autres exemples de code sont disponibles sur GitHub

1 commentaires

 
GN⁺ 2023-10-09
Commentaires sur Hacker News
  • Pour un runtime de 40 Ko, l’ensemble de fonctionnalités est étonnamment riche : un langage proche de Python/Ruby tourne dans une VM avec ramasse-miettes, et prend en charge les styles procédural, orienté objet et fonctionnel.
    Même en le parcourant rapidement, il a l’air assez agréable à écrire. Ce qui se démarque particulièrement, c’est la possibilité de précréer des objets constants et de les placer en grande partie en ROM, en n’utilisant la RAM que pour les données réellement modifiables. À ma connaissance, MicroPython ou Lua n’ont pas cette approche, et sur des MCU où la ROM/le flash sont abondants mais où la RAM manque, cela fait une grosse différence.

    • MicroPython permet aussi de freezer des modules pour les stocker en flash ; dans ce cas, seul le bytecode précompilé est placé dans le flash.
      Si un module contient des données stockées sous forme d’objets immuables comme des chaînes ou bytes(), elles sont lues directement depuis le flash sans être d’abord copiées en RAM. En revanche, pour effectuer le freeze, il faut exécuter le code une fois sur un ordinateur de bureau.
      https://docs.micropython.org/en/latest/reference/constrained...
    • Après avoir lu cette explication, je suis retourné voir le site, et cela m’a paru bien plus complet et attrayant que la page d’accueil.
    • Comme l’a expliqué @snops, MicroPython peut exécuter du code depuis le flash sous forme de frozen code. En conséquence, il faut le compiler dans le binaire du firmware, ce qui ralentit le cycle de développement rapide habituel de MicroPython.
      Le flux pratique consiste donc à déployer le code au runtime jusqu’à ce que la base de code soit stable ou proche de la phase de produit, puis à ce moment-là à freezer le code dans le firmware. Un modèle encore plus pratique est proposé avec la prise en charge de mapfs : allouer une partie du flash au stockage du code, compiler Python en bytecode (mpy_cross), l’uploader dans cette zone et l’exécuter au runtime. La preuve de concept existe déjà, mais il reste des détails à régler avant l’intégration dans la branche principale.
      https://github.com/micropython/micropython/pull/8381
    • Cela a l’air assez intéressant et, à bien des égards, semblable à Lua, mais il y a aussi des différences notables. À noter qu’eLua avait aussi travaillé sur le placement transparent de certaines données en ROM.
      Voir les rotables dans https://eluaproject.net/doc/v0.9/en_arch_ltr.html
  • Berry est utilisé dans Tasmota : https://tasmota.github.io/docs/Berry/

    • Tasmota a l’air intéressant. Si vous l’avez utilisé ou comparé à toit/jaguar, je serais curieux d’avoir votre avis.
  • Ça a vraiment l’air bien. Personnellement, je pense que c’est proche de la meilleure documentation que j’aie vue jusqu’ici.
    J’aime particulièrement le « short manual », qui permet à un développeur expérimenté de se faire rapidement une idée du langage. Je ne connaissais pas Tasmota auparavant, mais je vais probablement chercher un prétexte pour l’essayer dans un projet.

  • Ce qui m’intéresse le plus, c’est de savoir comment il se compare à Lua en performances et en consommation mémoire, et jusqu’où le sandboxing est possible.
    Je me demande aussi s’il peut exécuter du code non fiable.

    • La comparaison avec Lua est ce qu’il y a de plus intéressant. La plupart des entreprises où j’ai travaillé utilisaient Python comme une sorte de proxy ou d’outil CLI bricolé, mais Lua était difficile à recommander parce que son orientation objet basée sur les métatables prêtait à confusion.
      Si ce langage constitue une alternative valable avec des structures simples et des constructeurs de base, je pourrais le recommander pour se débarrasser de scripts Python et de leurs dépendances.
    • Par défaut, le code peut ouvrir et écrire dans le système de fichiers, donc on ne peut pas exécuter tel quel du code non fiable.
      Le sandboxing doit être pris en charge par l’application. WebAssembly est une bonne alternative pour le sandboxing.
    • Si une gestion native des threads avec le mot-clé async était ajoutée, cela le distinguerait clairement de Lua pour les moteurs de jeu.
  • Quelque chose de presque identique, mais avec moins de paradigmes et un typage statique, serait vraiment excellent comme langage de configuration.
    Dans plusieurs projets nécessitant une configuration complexe, on a parfois permis d’utiliser Lua ou Starlark/Python au lieu de devoir écrire des centaines de lignes de YAML, et cela semblait nettement mieux. Mais l’autocomplétion et la réflexion manquent toujours. Je ne vois pas vraiment de candidat idéal pour cet usage, et presque tous les petits langages de script embarquables sont typés dynamiquement.

    • J’ai réfléchi à une direction similaire, mais plus proche d’une configuration fortement validée que du typage statique. Il vaut mieux pouvoir charger toute la configuration puis produire une liste de problèmes, et si c’est bien fait, cela devrait aussi permettre un bon support dans les éditeurs.
      Cela dit, https://dhall-lang.org/ montre qu’on peut traiter beaucoup de configurations avec un typage statique, et que les avantages sont importants. Il semble pouvoir être intégré programmatiquement dans plusieurs langages.
      https://docs.dhall-lang.org/howtos/How-to-integrate-Dhall.ht...
  • Ça a l’air bien. Même si, pour l’instant, je reste assez attiré par uLisp (http://www.ulisp.com), je suis content de voir ce genre de choses apparaître.

  • Si vous avez tendance à regarder d’abord des exemples de code pour vous faire une idée d’un langage, le répertoire d’exemples vaut le détour : https://github.com/berry-lang/berry/tree/master/examples

  • Ce serait très bien de proposer des bindings pour d’autres langages. Nous utilisons quickjs depuis Rust et cela fonctionne plutôt bien.
    Comme on peut n’exposer à la VM que ce que l’on veut, il est aussi possible d’exécuter du code non fiable.

  • Cela ressemble à un langage optimisé pour l’embarqué. La conception et la documentation sont bonnes, et il ne semble pas avoir de comportements étranges ou inattendus.
    La syntaxe est aussi agréablement minimale et correspond à mes goûts. Je le garderai sûrement en tête pour mon prochain projet ESP32.

    • Je vais l’essayer tout de suite. Je bricolais un kit de développement ESP-32 pour fabriquer des décorations d’Halloween clignotantes, et j’ai hâte de tester ça.
      Il semble vraiment bien adapté à cette niche, et j’aime surtout le fait qu’il fasse des choses pour lesquelles aucun équivalent évident dans d’autres langages ne me vient à l’esprit.
  • Je me demande s’il existe une approche standard pour afficher le nom des fonctions de script dans les frames d’une stack trace native.
    En cas de crash ou lors de l’utilisation d’un profileur CPU, j’aimerais savoir s’il est possible d’afficher ensemble la stack trace native et la stack trace du script.