7 Punkte von GN⁺ 2025-12-24 | Noch keine Kommentare. | Auf WhatsApp teilen
  • 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
    • Ausführung: make octane

Lizenz

  • Veröffentlicht unter der MIT-Lizenz
  • Das Urheberrecht am Quellcode liegt bei Fabrice Bellard und Charlie Gordon

Noch keine Kommentare.

Noch keine Kommentare.