Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 776 – Emscripten-Unterstützung

Autor:
Hood Chatham <roberthoodchatham at gmail.com>
Sponsor:
Łukasz Langa <lukasz at python.org>
Discussions-To:
Discourse thread
Status:
Entwurf
Typ:
Informational
Erstellt:
18. März 2025
Python-Version:
3.14
Post-History:
18. März 2025, 28. März 2025

Inhaltsverzeichnis

Zusammenfassung

Emscripten ist eine vollständige Open-Source-Compiler-Toolchain. Es kompiliert C/C++-Code in WebAssembly/JavaScript-Executable, für die Verwendung in JavaScript-Laufzeiten, einschließlich Browsern und Node.js. Die Sprache Rust pflegt ebenfalls ein Emscripten-Ziel.

Dieses PEP formalisiert die Hinzufügung von Tier 3 für Emscripten-Unterstützung in Python 3.14, was vom Steering Council am 25. Oktober 2024 genehmigt wurde. Die Ziele sind:

  1. Den aktuellen Zustand der CPython Emscripten Laufzeit zu beschreiben
  2. Den aktuellen Zustand der Pyodide Laufzeit zu beschreiben
  3. Kleinere Funktionen zu identifizieren, die von der Pyodide Laufzeit in die CPython Emscripten Laufzeit nach oben gestreamt werden sollen

Die hier identifizierten kleineren Funktionen sind alle Funktionen, die ohne ein PEP implementiert werden könnten. Wir diskutieren bedeutendere Laufzeitfunktionen, die wir gerne implementieren würden, aber wir verschieben Entscheidungen über diese Funktionen auf nachfolgende PEPs.

Motivation

Ein Webbrowser ist eine universelle Computerplattform, verfügbar unter Windows, macOS, Linux und jedem Smartphone.

Das Pyodide-Projekt unterstützt Emscripten Python seit 2018. Hunderttausende von Studenten haben Python über Pyodide durch Projekte wie Capytale und PyodideU gelernt. Pyodide wird auch zunehmend von Python-Paketen verwendet, um interaktive Dokumentationen bereitzustellen. Dies zeigt sowohl die Bedeutung als auch die Reife der Emscripten-Plattform.

Emscripten und WASI sind auch die einzigen unterstützten Plattformen, die eine sinnvolle Sandbox-Funktion bieten.

Emscripten-Plattforminformationen

„Pyodide“ vs. „Emscripten Python“

Für die Zwecke dieses Dokuments verwenden wir den Begriff „Emscripten Python“, um das in den Repository python/cpython gepflegte Emscripten Python zu bezeichnen, ohne nachgelagerte Ergänzungen. Wir stellen die im Emscripten Python vorhandenen Funktionen den im Pyodide vorhandenen Funktionen gegenüber.

Pyodide wird auf GitHub gepflegt und über jsDelivr, npm und GitHub Releases verteilt.

Emscripten Python wird nicht verteilt, aber es ist möglich, es durch Befolgen der Anweisungen im Devguide zu erstellen.

Hintergrund zu Emscripten

Emscripten besteht aus einem C- und C++-Compiler und Linker, die auf LLVM basieren, zusammen mit einer Laufzeit, die auf einer leicht angepassten musl libc basiert.

Emscripten ist eine POSIX-basierte Plattform. Es verwendet das WebAssembly-Binärformat und den WebAssembly Dynamic Linking Section.

Der emcc Compiler ist ein Wrapper um clang. Der emcc Linker ist ein Wrapper um wasm-ld (ebenfalls Teil der LLVM Toolchain).

Die Emscripten-Unterstützung für portable C/C++-Code-Quellenkompatibilität mit Linux ist ziemlich umfassend, mit bestimmten erwarteten Ausnahmen, die erläutert werden müssen. CPython unterstützt bereits die Kompilierung für Emscripten und erfordert nur eine sehr geringe Anzahl von Änderungen am normalen Linux-Ziel.

POSIX-Konformität

Emscripten ist eine POSIX-Plattform. Es gibt jedoch POSIX-APIs, die zwar existieren, aber bei Aufruf immer fehlschlagen, und POSIX-APIs, die überhaupt nicht existieren. Insbesondere gibt es Probleme mit Netzwerk-APIs und blockierendem E/A, und es gibt keine Unterstützung für fork(). Siehe Emscripten Portability Guidelines.

Emscripten-Executable können mit Threading-Unterstützung verknüpft werden, dies ist jedoch mit mehreren Einschränkungen verbunden.

  • Die Aktivierung von Threading erfordert, dass Websites mit speziellen Sicherheitsheadern bereitgestellt werden, die die Möglichkeit einer Spectre-artigen Informationslecks angeben. Diese Header sind eine Benutzerfreundlichkeitsgefährdung für Benutzer, die nicht tief mit der Web-Plattform vertraut sind.
  • Wenn ein Executable sowohl mit Threading als auch mit einem dynamischen Linker verknüpft ist, gibt Emscripten eine Warnung aus, dass die gemeinsame Nutzung von dynamischem Laden und pthreads experimentell ist. Dies kann zu Leistungsproblemen oder Abstürzen führen. Diese Probleme erfordern möglicherweise WebAssembly-Standardisierungsarbeiten zur Lösung.

Aufgrund dieser Einschränkungen standardisiert Pyodide eine pthreads-freie Build-Version von Python. Wenn ausreichende Nachfrage besteht, könnte später eine pthreads-Build-Version ohne dynamischen Linker hinzugefügt werden.

Entwicklungswerkzeuge

Emscripten-Entwicklungswerkzeuge werden unter Linux, Windows und macOS gleichermaßen gut unterstützt. Die Upstream-Tools umfassen:

  • Das Emscripten Software Developer Kit (emsdk), das zur Installation der Emscripten-Compiler-Toolchain (emcc) verwendet werden kann.
  • emcc ist ein C- und C++-Compiler, Linker und ein Sysroot mit Header für die Systembibliotheken. Die Systembibliotheken selbst werden auf die Schnelle basierend auf der angeforderten ABI generiert.
  • Node.js kann als „Emulator“ verwendet werden, um Emscripten-Programme von der Befehlszeile auszuführen. Diese Emulation funktioniert am besten unter Linux, mit macOS als nächster Option. Node.js ist der bequemste Weg, um Emscripten-Programme zu testen.
  • Es ist möglich, Emscripten-Programme in jedem Webbrowser auszuführen. Browser-Automatisierungswerkzeuge wie Selenium, Playwright oder Puppeteer können verwendet werden, um Funktionen zu testen, die nur im Browser verfügbar sind.

Pyodide-Werkzeuge

  • pyodide build kann verwendet werden, um Python-Pakete für die Ausführung auf Emscripten zu cross-kompilieren. Cross-Kompilierung funktioniert am besten unter Linux, es gibt experimentelle Unterstützung unter macOS, und unter Windows ist sie überhaupt nicht unterstützt.
  • pyodide venv kann eine virtuelle Umgebung erstellen, die in Pyodide ausgeführt wird.
  • pytest-pyodide kann Python-Code gegen verschiedene JavaScript-Laufzeiten testen.

cibuildwheel unterstützt das Erstellen von Wheels für das Emscripten-Ziel unter Verwendung von pyodide build.

Kurzfristig bleiben die Packaging-Tools von Pyodide im Pyodide-Repository. Es ist eine offene Frage, wo die Packaging-Tools von Pyodide langfristig leben sollen. Zwei sinnvolle Optionen wären, dass sie unter der pyodide Organisation bleiben oder in die pypa Organisation auf GitHub verschoben werden.

Emscripten-Anwendungslebenszyklus

Ein Emscripten „Binary“ besteht aus einem Paar von Dateien, einer .mjs Datei und einer .wasm Datei. Die .wasm Datei enthält den gesamten kompilierten C/C++/Rust-Code. Die .mjs Datei enthält den Lebenszyklus-Code zur Einrichtung der Laufzeit, zum Auffinden der .wasm Datei, zum Kompilieren, Instanziieren, Aufrufen der main() Funktion und zum Herunterfahren der Laufzeit beim Beenden. Sie enthält auch eine Implementierung für alle Systemaufrufe, einschließlich des Dateisystems, des dynamischen Linkers und jeglicher Logik zur Bereitstellung zusätzlicher Funktionalität von der JavaScript-Laufzeit für C-Code.

Die .mjs Datei exportiert eine einzelne JavaScript-Funktion bootstrapEmscriptenExecutable(), die die Laufzeit bootstrappt, die main() Funktion aufruft und ein API-Objekt zurückgibt, das zum Aufrufen von C-Funktionen verwendet werden kann. Jedes Mal, wenn sie aufgerufen wird, wird eine vollständige und unabhängige Kopie der Laufzeit mit ihrem eigenen separaten Adressraum erstellt.

Die bootstrapEmscriptenExecutable() nimmt eine große Anzahl von Laufzeiteinstellungen entgegen. Die vollständige Liste ist hier in der Emscripten-Dokumentation beschrieben. Die wichtigsten davon sind:

  • thisProgram: Der Wert von argv[0]. In Python gelangt dies in sys.executable.
  • arguments: Die Liste der String-Argumente, die an main() übergeben werden.
  • preRun: Eine Liste von Callbacks, die aufgerufen werden, nachdem die JavaScript-Laufzeit und das Dateisystem gebootstrappt wurden, aber bevor main() aufgerufen wird. Nützlich zum Einrichten des Dateisystems, von Umgebungsvariablen und Standard-Streams.
  • print / printErr : Initiale Handler für stdout und stderr. Sie sind zeilenweise gepuffert und ein flush() einer partiellen Zeile erzwingt eine zusätzliche neue Zeile. Wenn TTY-ähnliches Verhalten gewünscht ist, sollten die Standard-Stream-Geräte in einem preRun() Hook ersetzt werden.
  • onExit: Ein Handler, der aufgerufen wird, wenn die Laufzeit beendet wird.
  • instantiateWasm: Ein Callback, der aufgerufen wird, um das WebAssembly-Modul zu instanziieren. Das Überschreiben des WebAssembly-Instanziierungsprozesses über diese Funktion ist nützlich, wenn Sie andere benutzerdefinierte asynchrone Startaktionen oder Downloads haben, die parallel zur WebAssembly-Kompilierung durchgeführt werden können. Die Implementierung dieses Callbacks ermöglicht die Durchführung all dieser Dinge parallel.

Dateisystemeinrichtung

Die Standardbibliothek

Damit Python ausgeführt werden kann, benötigt es Zugriff auf die Standardbibliothek im Emscripten-Dateisystem. Es gibt mehrere mögliche Ansätze hierfür:

  • Der Emscripten-Linker verfügt über ein --preload-file Flag, das das Laden von Dateien automatisch handhabt. Informationen darüber, wie es funktioniert, sind hier verfügbar. Dies ist der einfachste Ansatz, aber Pyodide hat sich davon verabschiedet, da es die Dateien in ein benutzerdefiniertes Archivformat einbettet, das nicht mit Standardwerkzeugen verarbeitet werden kann.
  • Verwenden Sie für Node.js den NODEFS, um ein natives Verzeichnis mit den Dateien in das Emscripten-Dateisystem einzubinden. Dies ist die effizienteste Option, aber nur für Node verfügbar. Es ist eng damit vergleichbar, was WASI tut.
  • Legen Sie die Standardbibliothek in ein Zip-Archiv und verwenden Sie ZipImporter. Die Verwendung einer unkomprimierten Zip-Datei ermöglicht es dem Webserver und Client, eine bessere Komprimierung auf die Standardbibliothek selbst anzuwenden. Es verwendet auch die effizienteren nativen Dekompressionsalgorithmen des Browsers anstelle einer weniger effizienten WebAssembly-Dekomprimierung. Die Nachteile sind ein höherer Speicherbedarf und das Brechen von inspect & verschiedenen Tests, die nicht erwarten, dass die Standardbibliothek auf diese Weise verpackt wird.
  • Legen Sie die Standardbibliothek in ein unkomprimiertes Tar-Archiv und binden Sie es in ein schreibgeschütztes TARFS-Dateisystem ein, das auf der Tar-Datei basiert. Dies bietet die beste Speichernutzung, Laufzeitleistung und Übertragungsgröße der Optionen, die im Browser verwendet werden können. Der Nachteil ist, dass Emscripten selbst kein TARFS enthält, sodass eine nachgelagerte Implementierung erforderlich ist.

Pyodide verwendet den ZipImporter-Ansatz in jeder Laufzeit. Python verwendet den NODEFS-Ansatz, wenn es mit Node ausgeführt wird, und den ZipImporter-Ansatz für das Web-Beispiel. Wir werden diesen Ansatz fortsetzen.

Der ZipImporter bietet eine saubere Lösung für ein Bootstrapping-Problem: Die Python-Laufzeit ist in der Lage, eine breite Palette von Archivformaten zu entpacken, aber die Python-Laufzeit ist noch nicht einsatzbereit, bis die Standardbibliothek verfügbar ist. Da zipimport.py ein gefrorenes Modul ist, werden diese Probleme vermieden. Alle anderen Ansätze lösen das Bootstrapping-Problem, indem sie die Standardbibliothek mit JavaScript einrichten.

Drittanbieterpakete

Es ist auch notwendig, alle benötigten Pakete im Emscripten-Dateisystem verfügbar zu machen. Derzeit hat Emscripten CPython keine Unterstützung für Pakete. Pyodide verwendet zwei verschiedene Ansätze für Pakete:

  • Im Browser lädt Pyodide Wheels herunter und entpackt sie in das MEMFS site-packages Verzeichnis. Anschließend werden alle dynamischen Bibliotheken im Wheel vorab geladen. Die Arbeit des Herunterladens und Installierens aller Pakete wird bei jedem Start der Laufzeit wiederholt.
  • Der Pyodide python CLI-Einstiegspunkt bindet das gesamte Host-Dateisystem als NODEFS-Verzeichnisse ein, bevor er Python bootstrappt. Dies ermöglicht die normale Funktionsweise des virtuellen Umgebungsmechanismus. Pyodide-virtuelle Umgebungen enthalten eine angepasste Kopie von pip und eine benutzerdefinierte pip.conf, damit pip Pyodide Wheels installiert. Beim Start lädt der Pyodide python CLI alle Emscripten-dynamischen Bibliotheken vor, die sich im site-packages Verzeichnis befinden.

Konsolen- und interaktive Nutzung

stdin gibt standardmäßig immer EOF zurück, während stdout und stderr standardmäßig console.log und console.error aufrufen. Es ist möglich, Handler an bootstrapEmscriptenExecutable() zu übergeben, um die Standard-Streams zu konfigurieren, aber egal was, die E/A-Geräte haben ein unerwünschtes zeilenweises Pufferverhalten, das eine neue Zeile beim Spülen erzwingt. Um ein gut funktionierendes TTY im Browser zu implementieren, ist es notwendig, die Standard-E/A-Geräte zu entfernen und sie in einem preRun Hook zu ersetzen.

Die korrekte Funktionsweise von stdin im Browser stellt eine zusätzliche Herausforderung dar, da es im Haupt-Thread des Browsers nicht erlaubt ist, auf Benutzereingaben zu warten. Wenn Emscripten in einem Web-Worker ausgeführt und mit den Shared-Memory-Headern bereitgestellt wird, ist es möglich, über Shared Memory und Atomics Eingaben zu empfangen. Es ist auch möglich, dass ein stdin Gerät auf eine einfachere und effizientere Weise blockiert, indem es den Stack umschaltet und die experimentelle JavaScript Promise Integration API verwendet.

Pyodide ersetzt die Standard-E/A-Geräte, um das zeilenweise Pufferverhalten zu beheben. Wenn Pyodide in Node.js ausgeführt wird, sind stdin, stdout und stderr standardmäßig mit process.stdin, process.stdout und process.stderr verbunden, und somit funktionieren die Standard-Streams out-of-the-box als TTY. Pyodide stellt auch sicher, dass shutil.get_terminal_size Ergebnisse zurückgibt, die mit process.stdout.rows und process.stdout.columns konsistent sind. Pyodide hat derzeit keine Unterstützung für das Stack-Switching von stdin.

Derzeit verwendet der Emscripten Python Node.js-Runner die Standard-E/A, die Emscripten bereitstellt. Das Web-Beispiel verwendet Atomics für stdin und hat benutzerdefinierte stdout und stderr Handler, aber sie weisen das unerwünschte zeilenweise Pufferverhalten auf. Wir werden das Standard-Stream-Verhalten von Pyodide nach oben streamen.

Langfristig hoffen wir, Stack-Switching-basierte stdin Geräte zu implementieren, aber das liegt außerhalb des Rahmens dieses PEP.

Abstürze und unbehandelte Ausnahmen

Wir betrachten den Zustand der C-Laufzeit als beschädigt, wenn ein WebAssembly-Trap, eine unbehandelte JavaScript-Ausnahme oder eine unbehandelte WebAssembly-Throw-Instruktion auftritt.

Im Gegensatz zu anderen Plattformen gibt es kein Betriebssystem, das die ausführbare Datei herunterfährt, wenn ein Trap oder eine andere nicht behebbare Beschädigung der libc-Laufzeit auftritt. Wir müssen unseren eigenen Code bereitstellen, um Tracebacks auszugeben, den Speicher zu dumpen oder was auch immer zur Fehlerbehebung eines Absturzes nützlich ist. Wenn wir eine JavaScript-API bereitstellen, müssen wir auch sicherstellen, dass diese nach einem nicht behebbaren Absturz deaktiviert wird, um zu verhindern, dass nachgelagerte Benutzer die Python-Laufzeit in einem inkonsistenten Zustand beobachten.

Um fatale Fehler zu erkennen, verwendet Pyodide den folgenden Ansatz: Alle aufrufbaren Aufrufe von WebAssembly an JavaScript werden mit einem JavaScript try/catch-Block umschlossen. Alle abgefangenen JavaScript-Ausnahmen werden in Python-Ausnahmen übersetzt. Dies stellt sicher, dass jede wiederherstellbare JavaScript-Fehler abgefangen wird, bevor sie durch WebAssembly-Frames zurückverfolgt wird. Alle Einstiegspunkte zu WebAssembly sind ebenfalls mit JavaScript try/catch-Blöcken umschlossen. Alle dort abgefangenen Ausnahmen haben zurückverfolgte WebAssembly-Frames und werden daher als fatale Fehler betrachtet (obwohl es einen Sonderfall für exit() gibt). Dies erfordert eine grundlegende Integration mit dem Python/JavaScript Foreign Function Interface.

Wenn die Pyodide-Laufzeit eine fatale Ausnahme abfängt, analysiert sie den Fehler, um festzustellen, ob er von einem Trap, einem Logikfehler in einem Systemaufruf, einem setjmp() ohne longjmp() oder einem libcxxabi-Aufruf an __cxa_throw() (einer unbehandelten C++-Ausnahme oder einem Rust-Panic) stammt. Wir rendern eine so informative Fehlermeldung wie möglich. Wir rufen auch _Py_DumpTraceback() auf, um zusätzlich zum JS/WebAssembly-Traceback einen Python-Traceback anzuzeigen. Außerdem wird die JavaScript-API deaktiviert, so dass weitere Versuche, Python aufzurufen, zu einer Fehlermeldung führen, dass die Laufzeit fatal fehlgeschlagen ist.

Normalerweise werden WebAssembly-Symbole gestrippt, sodass die WebAssembly-Frames nicht sehr nützlich sind. Die Kompilierung und Verknüpfung mit -g2 (oder einer höheren Debug-Einstellung) stellt sicher, dass WebAssembly-Symbole enthalten sind und im Traceback erscheinen.

Da Emscripten Python derzeit keine JavaScript-API und keine Foreign Function Interface hat, ist die Situation viel einfacher. Der Python Node.js-Runner umschließt den Aufruf von bootstrapEmscriptenExecutable() in einem try/catch-Block. Wenn eine Ausnahme abgefangen wird, zeigt er die JavaScript-Ausnahme an und ruft _Py_DumpTraceback() auf. Anschließend wird mit Code 1 beendet. Wir werden bei diesem Ansatz bleiben, bis wir entweder eine JavaScript-API oder ein Foreign Function Interface hinzufügen, was außerhalb des Rahmens dieses PEP liegt.

Spezifikation

Umfang der Arbeit

Die Hinzufügung von Emscripten als Tier 3-Plattform erfordert nur die Unterstützung für das Kompilieren eines Emscripten-kompatiblen Builds aus dem unveränderten CPython-Quellcode. Es ist nicht unbedingt erforderlich, dass offizielle verteilte Emscripten-Artefakte auf python.org vorhanden sind, obwohl diese in Zukunft hinzugefügt werden könnten. Kurzfristig werden sie weiterhin nachgelagert mit Pyodide vertrieben.

Emscripten wird mit demselben Konfigurations- und Makefile-System wie andere POSIX-Plattformen erstellt und muss daher auf einer POSIX-Plattform erstellt werden. Sowohl Linux als auch macOS werden unterstützt.

Ein Python-CLI-Einstiegspunkt wird bereitgestellt, der unter anderem zur Ausführung der Testsuite verwendet werden kann.

Verknüpfung

Es ist nur unterstützt, den Python-Interpreter statisch zu verknüpfen. Wir verwenden EM_JS-Funktionen im Interpreter für verschiedene Zwecke. Es ist möglich, Objektdateien dynamisch zu verknüpfen, die EM_JS-Funktionen enthalten, aber ihr Verhalten weicht erheblich von ihrem Verhalten in statischen Builds ab. Aus diesem Grund würde die Unterstützung spezielle Arbeit erfordern. Wenn ein Anwendungsfall für die dynamische Verknüpfung des Interpreters in Emscripten auftritt, können wir den erforderlichen Aufwand bewerten.

Standardbibliothek

Nicht unterstützte Module

Siehe https://pyodide.org/en/stable/usage/wasm-constraints.html#removed-modules.

Entfernte Module

Die folgenden Module werden aus der Standardbibliothek entfernt, um die Downloadgröße zu reduzieren und da sie derzeit nicht in der WebAssembly VM funktionieren würden.

  • curses
  • dbm
  • ensurepip
  • fcntl
  • grp
  • idlelib
  • msvcrt
  • pwd
  • resource
  • syslog
  • termios
  • tkinter
  • turtle
  • turtledemo
  • venv
  • winreg
  • winsound
Enthaltene, aber nicht funktionierende Module

Die folgenden Module können importiert werden, sind aber nicht funktionsfähig.

  • multiprocessing
  • threading
  • sockets

sowie alle Funktionalitäten, die diese erfordern.

Die folgenden sind vorhanden, können aber aufgrund einer Abhängigkeit vom entfernten termios-Modul nicht importiert werden.

  • pty
  • tty

Plattformidentifizierung

sys.platform gibt "emscripten" zurück. Obwohl Emscripten versucht, mit Linux kompatibel zu sein, sind die Unterschiede signifikant genug, dass ein eigener Name gerechtfertigt ist. Dies steht im Einklang mit dem Rückgabewert von os.uname().

Es gibt auch sys._emscripten_info, das die Emscripten-Version und die Laufzeit enthält (entweder navigator.userAgent im Browser oder "Node js" + process.version in Node.js).

Signalunterstützung

WebAssembly hat keine native Unterstützung für Signale. Darüber hinaus ist bei einer Nicht-pthreads-Build-Version der Adressraum des WebAssembly-Moduls nicht gemeinsam genutzt, so dass es für jeden Thread, der einen Interrupt sehen kann, unmöglich ist, den Eval-Brecher zu schreiben, während der Python-Interpreter Code ausführt. Um dies zu umgehen, gibt es zwei mögliche Lösungen:

  • Wenn Emscripten in einem Web-Worker ausgeführt und mit den Shared-Memory-Headern bereitgestellt wird, ist es möglich, Shared Memory außerhalb des WebAssembly-Adressraums als Signalpuffer zu verwenden. Ein Signalbehandlungs-UI-Thread kann das gewünschte Signal in den Signalpuffer schreiben. Der Interpreter kann den Zustand dieses Signalpuffers im Eval-Brecher-Code periodisch überprüfen. Das Überprüfen des Signalpuffers ist im Vergleich zum Überprüfen des Eval-Brechers auf nativen Plattformen langsam, daher tun wir dies nur einmal alle 50 Schleifendurchläufe im Eval-Brecher. Siehe Python/emscripten_signal.c
  • Durch die Verwendung von Stack Switching können wir gelegentlich den Stack umschalten und die JavaScript-Ereignisschleife durchlaufen lassen, dann den Zustand eines Signalpuffers überprüfen. Dies erfordert die experimentelle JavaScript Promise Integration API und würde am besten mit den Techniken zur Optimierung langer Aufgaben verwendet werden, die in diesem Artikel beschrieben werden.

Emscripten Python hat die Lösung basierend auf Shared Memory bereits implementiert und sie wird in Pyodide verwendet.

Schließlich hoffen wir, Stack-Switching-basierte Signale zu implementieren, damit es möglich ist, Signale im Haupt-Thread von Node und im Browser zu verwenden, sowie auf Webseiten, die nicht mit Shared-Memory-Headern bereitgestellt werden. Wir müssen auch den Shared-Memory-basierten Ansatz beibehalten, sowohl aus Gründen der Abwärtskompatibilität als auch weil er effizienter ist, wenn er möglich ist. Dies liegt jedoch außerhalb des Rahmens dieses PEP.

Funktionszeiger-Casts

Abschnitt 6.3.2.3, Absatz 8 des C-Standards besagt:

Ein Zeiger auf eine Funktion eines Typs kann in einen Zeiger auf eine Funktion eines anderen Typs konvertiert und wieder zurück konvertiert werden; das Ergebnis muss gleich dem ursprünglichen Zeiger sein. Wenn ein konvertierter Zeiger verwendet wird, um eine Funktion aufzurufen, deren Typ nicht mit dem Zeigertyp kompatibel ist, ist das Verhalten undefiniert.

Die meisten Plattformen verhalten sich jedoch gleich: Wenn eine Funktion mit zu vielen Argumenten aufgerufen wird, werden die zusätzlichen Argumente ignoriert; wenn eine Funktion mit zu wenigen Argumenten aufgerufen wird, werden die zusätzlichen Argumente mit Müll aufgefüllt.

Andererseits definiert die WebAssembly-Spezifikation den Aufruf einer Funktion mit falscher Signatur als Trap (siehe Schritt 18 bei der Ausführung von call_indirect).

Es ist üblich, dass Python-Erweiterungsmodule eine Funktion auf einen anderen Signaturentyp umwandeln und diese mit der anderen Signatur aufrufen. So definieren viele C-Erweiterungen eine METH_NOARGS-Funktion, um 0 oder 1 Argument zu akzeptieren. Der Interpreter ruft sie mit zwei Argumenten auf, von denen das erste das Python-Modulobjekt ist und das zweite immer NULL ist. Um diese Erweiterungsmodule ohne Änderung ihres Quellcodes zum Laufen zu bringen, benötigen wir spezielle Handhabung.

Anfänglich lösten wir dieses Problem, indem wir zu JavaScript aufriefen und JavaScript die Funktion aufrufen ließen. Beim Aufruf einer WebAssembly-Funktion aus JavaScript werden fehlende Argumente als Null behandelt und zusätzliche Argumente ignoriert (siehe Schritt 7 hier. Dies funktioniert, hat aber den Nachteil, langsam zu sein und das Stack Switching zu unterbrechen – es ist nicht möglich, durch JavaScript-Frames zu wechseln.

Mithilfe der wasm-gc ref.test-Instruktion können wir den Typ des Funktionszeigers abfragen und die Argumentliste manuell korrigieren.

wasm-gc ist eine relativ neue Funktion für WebAssembly-Laufzeiten. Daher versuchen wir, ein wasm-gc-basiertes Funktionszeiger-Cast-Trampolin zu verwenden, wenn möglich, und greifen andernfalls auf ein JS-Trampolin zurück. Jede JavaScript-Laufzeit, die Stack Switching unterstützt, unterstützt auch wasm-gc, so dass dies sicherstellt, dass Stack Switching auf jeder unterstützten Plattform-Laufzeit funktioniert. Das einzige Problem ist, dass iOS 18 eine fehlerhafte Implementierung von wasm-gc ausliefert, sodass wir diese gesondert behandeln müssen.

Die vollständigen Implementierungsdetails finden Sie hier.

Die Handhabung von Funktionszeiger-Casts ist vollständig in cpython implementiert. Pyodide verwendet genau denselben Code wie Upstream.

CI-Ressourcen

Pyodide kann auf jedem Linux mit einer relativ aktuellen Version von Node.js erstellt und getestet werden. Anaconda hat angeboten, physische Hardware für Emscripten Buildbots bereitzustellen, die von Russell Keith-Magee betreut werden.

CPython testet Tier 3-Plattformen derzeit nicht auf GitHub Actions, aber wenn sich dies jemals ändert, können seine Linux-Runner Emscripten Python erstellen und testen.

PEP 11

PEP 11 wird aktualisiert, um anzugeben, dass Emscripten unterstützt wird, insbesondere die Triples wasm32-unknown-emscripten_xx_xx_xx.

Russell Keith-Magee wird als erster Kernteam-Ansprechpartner für diese ABIs fungieren.

Zukünftige Arbeit

Verbesserung von Cross-Builds im Packaging-Ökosystem

Python unterstützt nun vier nicht-selbst-hostende Plattformen: iOS, Android, WASI und Emscripten. Alle davon müssen Pakete über Cross-Builds erstellen. Derzeit ermöglicht pyodide-build das Erstellen einer sehr großen Anzahl von Python-Paketen für Emscripten, aber es ist sehr kompliziert. Idealerweise hätte das Python-Packaging-Ökosystem Standards für Cross-Builds. Dies ist ein schwieriges Langzeitprojekt, insbesondere weil das Packaging-System komplex ist und von Grund auf mit der Annahme entwickelt wurde, dass Cross-Kompilierung nicht stattfinden würde.

Pyodide-Laufzeitfunktionen, die nach oben gestreamt werden sollen

Dies ist eine Sammlung von Pyodide-Laufzeitfunktionen, die außerhalb des Rahmens dieses PEP und des Python 3.14-Entwicklungszyklus liegen, die wir aber zukünftig nach oben streamen möchten.

JavaScript-API zum Bootstrapping

Derzeit bieten wir keine stabile API zum Bootstrapping von Python an. Stattdessen verwenden wir eine Sammlung von Einstellungen für den Node.js CLI-Einstiegspunkt und eine separate Sammlung von Einstellungen für die Browser-Demo.

Die Emscripten-Executable-Startup-API ist kompliziert und es gibt viele mögliche Konfigurationen, die fehlerhaft sind. Pyodide bietet eine einfachere Auswahl an Optionen als Emscripten. Dies gibt nachgeschalteten Benutzern viel Flexibilität und ermöglicht es uns, eine kleine Anzahl getesteter Konfigurationen beizubehalten. Außerdem reduziert es die doppelte Code-Erstellung nachgelagerter Systeme.

Schließlich möchten wir die Bootstrapping-API von Pyodide nach oben in die Hauptentwicklung integrieren. Kurzfristig werden wir zur Vereinfachung keine JavaScript-API unterstützen.

JavaScript Foreign Function Interface (FFI)

Da Emscripten POSIX unterstützt, kann eine erhebliche Anzahl von Aufgaben mit dem Modul os erledigt werden. Viele grundlegende Operationen in JavaScript-Runtimes sind jedoch nicht über POSIX-APIs möglich. Der Ansatz von Pyodide besteht darin, eine Abbildung zwischen dem JavaScript-Objektmodell und dem Python-Objektmodell sowie eine Aufrufkonvention zu definieren, die eine hochrangige bidirektionale Integration ermöglicht. Siehe die Pyodide-Dokumentation.

Asyncio

Die meisten JavaScript-Primitive sind asynchron. Der JavaScript-Thread, in dem Python läuft, verfügt bereits über eine Ereignisschleife. Es ist nicht allzu schwierig, eine Python-Ereignisschleife zu implementieren, die alle tatsächlichen Arbeiten an die JavaScript-Ereignisschleife delegiert, hier in Pyodide implementiert.

Dies hängt logischerweise davon ab, dass es mindestens eine begrenzte JavaScript FFI gibt, da der einzige Weg, Aufgaben auf der JavaScript-Ereignisschleife zu planen, über einen Aufruf an JavaScript erfolgt.

Eine Ursache für Inkompatibilität ist, dass es nicht möglich ist, den Lebenszyklus der Ereignisschleife von einem JavaScript-Isolaten aus zu steuern. Dies führt dazu, dass asyncio.run() und ähnliches nicht funktionieren.

Durch Stack-Switching ist es auch möglich, aus „synchronen“ Python-Frames eine Coroutine zu machen. Diese Stack-Switching-Coroutinen werden auf derselben Ereignisschleife wie normale Python-Coroutinen geplant und sind vollständig reentrant. Dies ist in Pyodide vollständig implementiert.

Abwärtskompatibilität

Das Hinzufügen einer neuen Plattform führt keine Rückwärtskompatibilitätsprobleme für CPython selbst mit sich. Es kann jedoch einige Rückwärtskompatibilitätsimplikationen für Pyodide-Benutzer geben. Es gibt eine große Anzahl bestehender Benutzer von Pyodide, daher ist es wichtig, wenn Features von Pyodide in Python integriert werden, sorgfältig darauf zu achten, Rückwärtsinkompatibilitäten zu minimieren. Wir werden auch eine Möglichkeit benötigen, teilweise integrierte Features zu deaktivieren, damit Pyodide sie durch vollständigere Versionen nachgelagerter Systeme ersetzen kann.

Sicherheitsimplikationen

Das Hinzufügen einer neuen Plattform führt keine neuen Sicherheitsimplikationen mit sich.

Emscripten und WASI sind auch die einzigen unterstützten Plattformen, die Sandboxing bieten. Wenn Benutzer nicht vertrauenswürdigen Python-Code oder nicht vertrauenswürdige Python-Erweiterungsmodule ausführen möchten, bietet Emscripten ihnen eine sichere Möglichkeit dazu.

Wie man das lehrt

Die Bildungsbedürfnisse im Zusammenhang mit diesem PEP betreffen zwei Entwicklergruppen.

Erstens müssen Webentwickler wissen, wie sie Python erstellen und in einer Website verwenden, zusammen mit ihrem eigenen Python-Code und unterstützenden Paketen, und wie sie all dies zur Laufzeit verwenden. Die Dokumentation wird dies in ähnlicher Form wie das bestehende Windows-Einbettungspaket abdecken. Kurzfristig werden wir Entwickler ermutigen, Pyodide zu verwenden, wenn dies überhaupt möglich ist.

Referenzimplementierung

Pyodide.


Quelle: https://github.com/python/peps/blob/main/peps/pep-0776.rst

Zuletzt geändert: 2025-05-16 14:48:26 GMT