4 Punkte von GN⁺ 2023-10-09 | 1 Kommentare | Auf WhatsApp teilen
  • Berry ist eine dynamisch typisierte Skriptsprache für leistungsschwache Embedded-Geräte; auf einem ARM Cortex M4 ist der Interpreter-Core kleiner als 40 KiB und kann auch mit weniger als 4 KiB Heap ausgeführt werden
  • Die Runtime besteht aus einem One-Pass-Compiler und einer registerbasierten VM; der gesamte Code ist in ANSI C99 geschrieben und lässt sich daher leicht in bestehenden Embedded-Code integrieren
  • Aus Performance-Gründen werden int, real, boolean und string nicht als Klassenobjekte umgesetzt; nur list, map und range werden als Klassenobjekte behandelt
  • Die Sprachfunktionen unterstützen imperative, objektorientierte und funktionale Programmierung und umfassen GC, FFI, Exception Handling, Bytecode-Dateien und Erweiterungsmodule
  • Durch Objektkonstruktion zur Compile-Zeit und Fixierung im Flash lässt sich die RAM-Nutzung von Code, Klassen und Modulen reduzieren, was gut zu stark speicherbeschränkten Umgebungen passt

Runtime für Embedded-Umgebungen

  • Berry ist eine dynamisch typisierte Skriptsprache für Embedded-Geräte mit geringer Leistung, etwa Mikroprozessoren
    • Auf Basis einer ARM Cortex M4 CPU, Thumb ISA und des ARMCC-Compilers liegt die Codegröße des Interpreter-Cores unter 40 KiB
    • Unter denselben Bedingungen ist die Ausführung mit weniger als 4 KiB Heap möglich
  • Der Interpreter ist in ANSI C99 geschrieben und enthält einen One-Pass-Bytecode-Compiler sowie eine registerbasierte virtuelle Maschine
  • Das Typdesign priorisiert Laufzeit-Performance und unterscheidet zwischen Werttypen und Objekttypen
    • Typen, die keine Klassenobjekte sind: int, real, boolean, string
    • Typen, die Klassenobjekte sind: list, map, range
  • Die Objektkonstruktion zur Compile-Zeit speichert die meisten konstanten Objekte im schreibgeschützten Code-Datensegment und senkt so beim Start des Interpreters die RAM-Nutzung

Sprachfunktionen und Ausführungsmodell

  • Die Basistypen umfassen nil, boolean, integer, real, string, class, instance, module, list, map und range
  • Ausdrücke und Operatoren unterstützen Zuweisung, relationale, logische, arithmetische und bitweise Operationen, Felder, Indizes, String-Verkettung, den bedingten Operator, Klammern und bytes buffer
  • Kontrollstrukturen sind if elif else end, while, for, break und continue
  • Die Funktionsfunktionen umfassen lokale Variablen und Block-Scope, Rückgabeanweisungen, verschachtelte Funktionsdefinitionen, Upvalue-basierte Closures, anonyme Funktionen und Lambda-Ausdrücke
  • Klassen unterstützen nur öffentliche Einfachvererbung und bieten Methoden, Operator-Overloading, Konstruktormethoden und Destruktormethoden
  • Der GC verwendet Mark-Sweep GC
  • Exception Handling kann mit der raise-Anweisung beliebige Exception-Werte werfen und unterstützt mehrere catch-Modi
  • Mit der Bytecode-Dateifunktion können Funktionen in Bytecode-Dateien exportiert und anschließend wieder geladen und ausgeführt werden

Module, C-Integration und Beispiele

  • Die Modulverwaltung unterstützt sowohl eingebaute Module als auch Erweiterungsmodule
    • Erweiterungsmodule umfassen Skriptmodule, Bytecode-Dateimodule und Shared-Library-Module wie *.so und *.dll
    • Code, Klassen und Module können im Flash fixiert werden, um die RAM-Nutzung zu reduzieren
    • Optional werden Regex-Unterstützung und optionales LVGL mapping angeboten
  • Über das Native C Interface lässt es sich wie eine Bibliothek in bestehenden Code einbetten; Code wie Tasmota dient als Beispiel
    • Optionales easy mapping wird bereitgestellt, um aus Berry heraus C-Code aufzurufen
  • Die Syntaxbeispiele zeigen das Durchlaufen von Listen, das Erstellen von maps und Durchlaufen von Schlüsseln, class-Deklarationen sowie Einfachvererbung über den Operator :
  • Weitere Beispielquellen sind auf GitHub verfügbar

1 Kommentare

 
GN⁺ 2023-10-09
Hacker-News-Kommentare
  • Für eine 40-KB-Runtime ist der Funktionsumfang erstaunlich groß: Auf einer VM mit Garbage Collection läuft eine Python/Ruby-ähnliche Sprache, und sie unterstützt prozedurale, objektorientierte und funktionale Stile.
    Schon auf den ersten Blick wirkt sie ziemlich angenehm zu schreiben. Besonders auffällig ist die Möglichkeit, konstante Objekte vorab zu erzeugen und größtenteils im ROM abzulegen, während RAM nur für tatsächlich veränderbare Daten genutzt wird. Soweit ich weiß, gibt es so etwas weder in MicroPython noch in Lua; bei MCUs mit reichlich ROM/Flash, aber knappem RAM macht das einen großen Unterschied.

    • Auch MicroPython kann Module freezen und im Flash speichern; dabei landet im Flash nur vorab kompilierter Bytecode.
      Wenn ein Modul gespeicherte Daten in Form unveränderlicher Objekte wie Strings oder bytes() enthält, werden sie direkt aus dem Flash gelesen, ohne vorher in den RAM kopiert zu werden. Allerdings muss man zum Freezen den Code einmal auf einem Desktop-Computer ausführen.
      https://docs.micropython.org/en/latest/reference/constrained...
    • Nachdem ich die Beschreibung gelesen und mir die Seite noch einmal angesehen habe, wirkt sie auf mich deutlich umfassender und attraktiver als die Homepage.
    • Wie @snops erklärt hat, kann MicroPython Code in Form von frozen code aus dem Flash ausführen. Dadurch muss er allerdings in das Firmware-Binary einkompiliert werden, was den üblichen schnellen Entwicklungszyklus von MicroPython verlangsamt.
      Ein angenehmer Workflow ist daher, den Code zur Laufzeit auszuliefern, bis die Codebasis stabil ist oder sich der Produktphase nähert, und den Code dann in die Firmware zu freezen. Als noch komfortableres Modell gibt es einen Vorschlag für mapfs-Unterstützung: Ein Teil des Flash wird für Code-Speicher reserviert, Python wird zu Bytecode kompiliert (mpy_cross), in diesen Bereich hochgeladen und kann zur Laufzeit ausgeführt werden. Ein Proof of Concept existiert bereits, aber vor der Aufnahme in den Mainline-Zweig sind noch Details zu bereinigen.
      https://github.com/micropython/micropython/pull/8381
    • Sieht ziemlich interessant aus und ähnelt in vielerlei Hinsicht Lua, hat aber auch auffällige Unterschiede. Nebenbei: Auch bei eLua gab es Arbeiten daran, bestimmte Daten transparent im ROM abzulegen.
      Siehe die rotables unter https://eluaproject.net/doc/v0.9/en_arch_ltr.html
  • Berry wird in Tasmota verwendet: https://tasmota.github.io/docs/Berry/

    • Tasmota sieht interessant aus. Falls du es ausprobiert oder mit toit/jaguar verglichen hast, würde mich deine Einschätzung interessieren.
  • Sieht wirklich gut aus. Persönlich halte ich die Dokumentation für nahezu erstklassig, verglichen mit vielem, was ich bisher gesehen habe.
    Besonders gut ist das „short manual“, das erfahrenen Entwicklern schnell ein Gefühl für die Sprache vermittelt. Tasmota kannte ich vorher nicht, aber ich werde wohl nach einem Vorwand suchen, es in einem Projekt auszuprobieren.

  • Am meisten interessiert mich, wie Performance und Speicherverbrauch im Vergleich zu Lua aussehen und wie gut Sandboxing möglich ist.
    Ich frage mich auch, ob man nicht vertrauenswürdigen Code ausführen kann.

    • Der Vergleich mit Lua ist am spannendsten. Die Firmen, bei denen ich gearbeitet habe, haben Python meistens wie einen holprigen Proxy oder ein CLI-Tool eingesetzt, aber Lua war wegen seiner metatabellenbasierten Objektorientierung schwer zu empfehlen.
      Wenn diese Sprache mit einfachen Structs und Standardkonstruktoren eine brauchbare Alternative ist, könnte ich sie empfehlen, um Python-Skripte und deren Abhängigkeiten loszuwerden.
    • Standardmäßig kann Code das Dateisystem öffnen und beschreiben, daher kann nicht vertrauenswürdiger Code nicht einfach so ausgeführt werden.
      Sandboxing muss die Anwendung übernehmen. Für Sandboxing ist WebAssembly eine gute Alternative.
    • Wenn native Thread-Verwaltung mit einem async-Schlüsselwort hinzukäme, würde sie sich für Game-Engine-Einsätze klar von Lua abheben.
  • Etwas fast Genaues in dieser Art, aber mit weniger Paradigmen und statischer Typisierung, wäre als Konfigurationssprache wirklich großartig.
    In mehreren Projekten mit komplexer Konfiguration gab es die Möglichkeit, statt hunderter Zeilen YAML Lua oder Starlark/Python zu verwenden, und das fühlte sich deutlich besser an. Allerdings fehlten mir immer Autovervollständigung und Reflection. Für diesen Zweck sehe ich kaum passende Kandidaten, und fast alle kleinen einbettbaren Skriptsprachen sind dynamisch typisiert.

    • Ich denke in eine ähnliche Richtung, aber eher an stark validierte Konfiguration als an statische Typisierung. Besser wäre es, die gesamte Konfiguration zu laden und anschließend eine Liste von Problemen erzeugen zu können; wenn es gut umgesetzt ist, sollte auch gute Editor-Unterstützung möglich sein.
      Allerdings zeigt https://dhall-lang.org/, dass sich viele Konfigurationen statisch typisiert behandeln lassen und dass das große Vorteile hat. Es scheint sich programmatisch in mehrere Sprachen einbetten zu lassen.
      https://docs.dhall-lang.org/howtos/How-to-integrate-Dhall.ht...
  • Sieht gut aus. Trotzdem tendiere ich derzeit noch ziemlich zu uLisp (http://www.ulisp.com), aber es ist schön, dass so etwas erscheint.

  • Wenn man sich zuerst Codebeispiele ansieht, um ein Gefühl für eine Sprache zu bekommen, lohnt sich ein Blick in das Beispielverzeichnis: https://github.com/berry-lang/berry/tree/master/examples

  • Bindings für andere Sprachen wären großartig. Wir verwenden quickjs aus Rust heraus, und das funktioniert ziemlich gut.
    Da man der VM nur das bereitstellen kann, was man möchte, lässt sich auch nicht vertrauenswürdiger Code ausführen.

  • Wirkt wie eine Sprache, die für Embedded-Einsätze optimiert ist. Design und Dokumentation sind gut, und sie scheint nichts Seltsames oder Unerwartetes zu tun.
    Auch die Syntax ist angenehm minimalistisch und trifft meinen Geschmack. Ich werde sie bei meinem nächsten ESP32-Projekt definitiv im Hinterkopf behalten.

    • Ich werde sie direkt ausprobieren. Ich habe gerade mit einem ESP-32-Development-Kit herumgespielt, um blinkende Halloween-Deko zu bauen, und freue mich darauf, das hier zu testen.
      Für diese Nische scheint sie wirklich gut zu passen, vor allem weil sie Dinge tut, für die mir in anderen Sprachen keine direkten Entsprechungen einfallen.
  • Ich frage mich, ob es einen Standardansatz gibt, um in nativen Stacktraces bei Stackframes die Namen von Skriptfunktionen anzuzeigen.
    Bei Abstürzen oder beim Einsatz eines CPU-Profilers wüsste ich gern, ob sich native Stacktraces und Skript-Stacktraces kombiniert darstellen lassen.