- 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
mqjs lassen 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
mqjs und unterstützt Skriptausführung, Auswertung, interaktiven Modus, Setzen von Speicherlimits und Speichern von Bytecode
- Beispiel:
./mqjs --memory-limit 10k tests/mandelbrot.js
- Mit der Option
-o kann kompilierter Bytecode in einer Datei gespeichert werden
- Gespeicherter Bytecode kann mit
./mqjs mandelbrot.bin ausgeführt werden
- Bytecode hängt von der Endianness und Wortlänge (32/64 Bit) der CPU ab; mit der Option
-m32 lässt sich Bytecode für 32 Bit erzeugen
- Mit der Option
--no-column lassen sich Spaltennummern in Debuginformationen entfernen
Strict Mode
- Es ist nur der strict mode erlaubt; das Schlüsselwort
with kann nicht verwendet werden, und globale Variablen müssen zwingend mit var deklariert werden
- Arrays mit Lücken (holes) sind nicht erlaubt
- Beispiel:
a[10] = 2 löst einen TypeError aus
- Wenn ein Array mit Lücken benötigt wird, soll stattdessen ein normales Objekt verwendet werden
- 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 in iteriert nur über eigene Eigenschaften eines Objekts, for of wird 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, printf werden 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)
- 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()
- 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 über JS_LoadBytecode() und JS_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
Lizenz
- Veröffentlicht unter der MIT-Lizenz
- Das Urheberrecht am Quellcode liegt bei Fabrice Bellard und Charlie Gordon
Noch keine Kommentare.