Fabrice Bellard veröffentlicht MicroQuickJS
(github.com/bellard)- MicroQuickJS (MQuickJS) ist eine ultraleichte JavaScript-Engine für Embedded-Systeme und läuft mit nur etwa 10 kB RAM und 100 kB ROM
- Um bei ähnlicher Geschwindigkeit wie QuickJS den Speicherverbrauch zu senken, verwendet sie einen Tracing Garbage Collector und UTF-8 als String-Speicherformat
- Unterstützt wird eine eingeschränkte JavaScript-Teilmenge nahe ES5; zugelassen ist nur der strict mode, der fehleranfällige Syntax verbietet
- Mit dem REPL-Tool
mqjslassen sich Skripte ausführen, Bytecode speichern und Speicherlimits setzen; erzeugter Bytecode kann direkt aus dem ROM ausgeführt werden - Die gesamte Engine und die Standardbibliothek liegen im ROM und ermöglichen schnelle Initialisierung bei geringem Speicherverbrauch, was die Effizienz der JavaScript-Ausführung in Embedded-Umgebungen erhöht
Einführung
- MicroQuickJS (MQuickJS) ist eine JavaScript-Engine für Embedded-Systeme und läuft mit 10 kB RAM und 100 kB ROM (einschließlich ARM-Thumb-2-Code)
- Die Geschwindigkeit ist ähnlich wie bei QuickJS
- Es wird nur eine Teilmenge nahe ES5 unterstützt; die Engine arbeitet ausschließlich im strict mode, der ineffiziente oder fehleranfällige Syntax verbietet
- Zwar teilt sie einen Teil des Codes mit QuickJS, die interne Struktur wurde aber vollständig anders entworfen, um Speicher zu sparen
- Verwendet werden ein Tracing Garbage Collector, keine Nutzung des CPU-Stacks und UTF-8-Stringspeicherung
REPL
- Das REPL-Kommando lautet
mqjsund unterstützt Skriptausführung, Auswertung, interaktiven Modus, Setzen von Speicherlimits und Speichern von Bytecode- Beispiel:
./mqjs --memory-limit 10k tests/mandelbrot.js
- Beispiel:
- Mit der Option
-okann kompilierter Bytecode in einer Datei gespeichert werden- Gespeicherter Bytecode kann mit
./mqjs mandelbrot.binausgeführt werden
- Gespeicherter Bytecode kann mit
- Bytecode hängt von der Endianness und Wortlänge (32/64 Bit) der CPU ab; mit der Option
-m32lässt sich Bytecode für 32 Bit erzeugen - Mit der Option
--no-columnlassen sich Spaltennummern in Debuginformationen entfernen
Strict Mode
- Es ist nur der strict mode erlaubt; das Schlüsselwort
withkann nicht verwendet werden, und globale Variablen müssen zwingend mitvardeklariert werden - Arrays mit Lücken (holes) sind nicht erlaubt
- Beispiel:
a[10] = 2löst einen TypeError aus - Wenn ein Array mit Lücken benötigt wird, soll stattdessen ein normales Objekt verwendet werden
- Beispiel:
- Nur globales eval wird unterstützt, kein Zugriff auf lokale Variablen
- Value Boxing wird nicht unterstützt (
new Number(1)usw.)
JavaScript-Teilmenge
- Basierend auf strict mode, mit Fokus auf ES5-Kompatibilität
- Array-Objekte enthalten keine Lücken; Zugriffe auf Indizes außerhalb des Bereichs führen zu Fehlern
for initeriert nur über eigene Eigenschaften eines Objekts,for ofwird nur für Arrays unterstützt- Ein globales Objekt existiert, aber ohne Getter/Setter; direkt angelegte Eigenschaften werden nicht als globale Variablen sichtbar
- Reguläre Ausdrücke (Regexp) behandeln Groß-/Kleinschreibung nur für ASCII;
/./matched Unicode-Codepoints statt UTF-16-Einheiten - String-Funktionen verarbeiten nur ASCII (
toLowerCase,toUpperCase) - Date unterstützt nur
Date.now() - Zusätzlich unterstützt:
for of, Typed Arrays, String-Literale mit\u{hex}- Math-Funktionen:
imul,clz32,fround,trunc,log2,log10 - Exponentiationsoperator, Regexp-Flags (s, y, u), String-Funktionen (
replaceAll,trimStart,trimEnd), globalThis
C-API
- Minimale Abhängigkeit von der C-Bibliothek;
malloc,free,printfwerden nicht verwendet - Ein Speicherpuffer muss direkt bereitgestellt werden; die Engine allokiert Speicher ausschließlich innerhalb dieses Puffers
- Beispiel:
ctx = JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib)
- Beispiel:
- Durch die Garbage-Collection-Methode ist kein Aufruf von
JS_FreeValue()nötig - Objektadressen können sich bei jeder Allokation verschieben; daher wird die Verwendung von
JSValue-Zeigern empfohlen- Sichere Referenzverwaltung mit
JS_PushGCRef()/JS_PopGCRef()
- Sichere Referenzverwaltung mit
- Die Standardbibliothek wird als im ROM speicherbare C-Struktur kompiliert und ermöglicht schnelle Initialisierung bei geringem RAM-Verbrauch
- Bytecode-Ausführung ist aus dem ROM möglich, nach Relokation mit
JS_RelocateBytecode()und Ausführung überJS_LoadBytecode()undJS_Run() - Mathematikbibliothek (
libm.c) und Fließkomma-Emulator sind integriert
Interne Struktur und Vergleich mit QuickJS
- Garbage Collection: Statt Referenzzählung wird ein Tracing- und komprimierender GC verwendet
- Verhindert Speicherfragmentierung und verkleinert Objekte
- Wertdarstellung: Auf die CPU-Wortgröße (32/64 Bit) abgestimmt
- Kann 31-Bit-Integer, Unicode-Codepoints, Fließkommazahlen und Zeiger auf Speicherblöcke speichern
- Strings werden in UTF-8 gespeichert und sind effizienter als QuickJS' 8/16-Bit-Array-Ansatz
- C-Funktionen können als Einzelwert gespeichert werden; zusätzliche Eigenschaften sind nicht möglich
- Die Standardbibliothek liegt im ROM; durch minimale RAM-Objekte ist eine schnelle Engine-Initialisierung möglich
- Bytecode ist stackbasiert und wird über eine indirekte Referenztabelle schreibgeschützt behandelt
- Golomb-Code komprimiert Zeilen- und Spaltennummern
- Der Compiler ist QuickJS ähnlich, verwendet aber einen nichtrekursiven Parser, um den C-Stack-Verbrauch zu begrenzen
- Bytecode-Erzeugung in einem einzigen Durchlauf ohne Parse-Tree
Tests und Benchmarks
- Grundlegende Tests:
make test - QuickJS-Mikrobenchmarks:
make microbench - Der Octane-Benchmark (angepasste Version für strict mode) kann separat heruntergeladen werden
- Ausführung:
make octane
- Ausführung:
Lizenz
- Veröffentlicht unter der MIT-Lizenz
- Das Urheberrecht am Quellcode liegt bei Fabrice Bellard und Charlie Gordon
2 Kommentare
Hacker-News-Kommentare
Hätte es so etwas 2010 gegeben, wäre die Skriptsprache von Redis vermutlich nicht Lua, sondern JavaScript geworden.
Lua wurde nicht aus sprachlichen Gründen gewählt, sondern wegen Implementierungsbeschränkungen: klein, schnell und auf ANSI-C basierend.
Einige Ideen von Lua sind gut, aber persönlich fand ich die Abkehr von der Algol-artigen Syntax unnötig.
Bei SmallTalk oder FORTH lohnt sich die Verwirrung als Preis dafür, neue Abstraktionskonzepte zu lernen, aber bei Lua sehe ich keinen vergleichbaren Grund für diese Änderungen.
Lua ist die einzige leichtgewichtige Sprache mit Tail-Call-Optimierung (TCO), wodurch man Programme rein rekursiv und ohne Schleifen schreiben kann.
JavaScript hat diese Optimierung nicht, daher geht das dort nicht auf dieselbe Weise.
Lua eignet sich auch besonders gut für das Schreiben von Compilern, weil dort viele rekursive Strukturen vorkommen.
Für Redis-Skripting könnte JS besser passen, aber es ist schade, Lua deshalb abzuwerten.
In Brasilien waren Pascal und Ada weiter verbreitet als C, daher kommt dieser Einfluss.
Ruby und Perl erschienen in einer ähnlichen Zeit, versuchten aber deutlich radikalere Syntaxänderungen.
Parser und Lexer werden getrennt gehalten, trotzdem gab es kaum Versuche, einfach Tokens wie
{}gegenthen/endauszutauschen.Verwandte Diskussionen: HN-Thread, Reddit-Diskussion
Diese Engine beschränkt JS auf eine Weise, die ich mir früher bei der Arbeit an JSC gewünscht hätte.
Im Web sind solche Einschränkungen wegen der Kompatibilität unmöglich, aber in Embedded-Umgebungen kann genau das ein erfreuliches Design sein.
Ich habe einen Playground gebaut, in dem man MicroQuickJS direkt im Browser ausprobieren kann.
MicroQuickJS-WebAssembly-Version
Zur Referenz gibt es auch die ursprüngliche QuickJS-Version.
QuickJS ist 2,28 MB groß, MicroQuickJS dagegen nur 303 KB und damit deutlich leichter.
Mit der Option
emcc -O3oder zusätzlich--closure 1könnte man es vermutlich noch weiter verkleinern.QuickJS ist bereits optimiert, und nur bei MicroQuickJS gibt es noch Spielraum.
Wie Jeff Atwood berühmt sagte: „Jede Anwendung, die in JavaScript geschrieben werden kann, wird irgendwann in JavaScript geschrieben.“
Das scheint jetzt sogar für Embedded-Systeme zu gelten.
Jeff-Atwood-Wiki
JSLinux-Link
Schade, dass es ohne Commit-Historie hochgeladen wurde.
Ich hätte gern gesehen, wie schnell jemand auf diesem Niveau so ein Projekt fertigstellt.
Da es ohnehin auf QuickJS basiert, wäre ein Vergleich vermutlich aber nur begrenzt aussagekräftig.
Ich frage mich, ob das die leichtgewichtigste Methode sein könnte, um die YouTube-JS-Challenge von yt-dlp zu lösen.
Siehe dazu die yt-dlp-EJS-Dokumentation.
QuickJS wird bereits unterstützt.
Die JS-Puzzles von YouTube sind so komplex, dass sogar ein in Python geschriebener JS-Emulator aufgegeben wurde.
Ich kenne mich mit Embedded-Systemen nicht gut aus, aber ich frage mich, ob so eine Engine ESP32 oder Arduino in JavaScript programmierbar machen könnte.
So ähnlich wie MicroPython.
MicroQuickJS implementiert nur Teile von ES5 und bietet keine Umgebungs-Bindings.
Dort wurde JS-Code in Lua-VM-Bytecode umgewandelt und dann ausgeführt, was ein ziemlich cleverer Ansatz war.
Vor Kurzem habe ich sogar das alte Node-0.8-CLI in Rust neu geschrieben, aber am Ende wanderte die Hardware doch wieder in die Schublade.
malloc()auskommt. Das eröffnet Möglichkeiten.Timing ist wirklich entscheidend. Als es gestern Abend gepostet wurde, gab es überhaupt keine Reaktion.
Es gibt die Strategie, es morgens in den USA erneut zu posten oder es in regelmäßigen Abständen wieder einzureichen.
Fabrice Bellard ist einer der produktivsten und vielseitigsten Programmierer, die heute leben.
Bekannte Werke: FFmpeg, QEMU, JSLinux, TCC, QuickJS
Eine absolute Legende.
Beeindruckend ist dieser Ansatz, mit minimalen Abhängigkeiten und Werkzeugen vollständige Programme zu bauen.
Wenn es ein echter Mensch ist, muss er schließlich auch irgendwann schlafen.
ts_server, TextSynth
Es wirkt so, als bevorzuge er Programme, bei denen der Nutzer Parameter setzt und das Programm dann in sich geschlossen läuft.
Liste der IOCCC-Preisträger
Beeindruckend ist, dass man „JS sogar mit nur 10 kB RAM kompilieren und ausführen kann“.
Das ist perfektes Timing in einer Phase, in der RAM wieder teurer wird.
Ich frage mich, ob man das in Chromium oder Electron einbauen könnte.
Für eine Vorstellung von Fabrice Bellard siehe bitte meinen früheren Kommentar. Was für ein erstaunliches Monster, und dabei so beständig..
https://de.news.hada.io/topic?id=59#cid51