Hinweis
Diese Dokumentation ist etwas veraltet und befindet sich im Prozess der Aktualisierung.
Emscripten unterstützt das statische Linken von Objektdateien (und ar-Archiven, die Objektdateien enthalten). Dadurch können die meisten Build-Systeme mit Emscripten mit wenigen oder gar keinen Änderungen arbeiten (siehe Projekte erstellen).
Zusätzlich bietet Emscripten Unterstützung für eine Form des dynamischen Linkens von WebAssembly-Modulen. Dies kann zusätzlichen Overhead verursachen, weshalb für die beste Performance weiterhin statisches Linken bevorzugt werden sollte. Dieser Overhead kann jedoch durch die Verwendung bestimmter Kommandozeilen-Flags reduziert werden. Details finden Sie weiter unten.
Bevor wir zum dynamischen Linken kommen, lassen Sie uns über statisches Linken sprechen. Emscriptens Linking-Modell unterscheidet sich ein wenig von den meisten nativen Plattformen. Um es zu verstehen, bedenken Sie, dass native Linking-Modelle in einer Umgebung arbeiten, in der die folgenden Fakten zutreffen:
Die Anwendung läuft direkt auf dem lokalen System und hat Zugriff auf lokale Systembibliotheken, wie C- und C++-Standardbibliotheken und andere.
Die Codegröße ist kein großes Problem. Dies liegt zum Teil daran, dass die Systembibliotheken bereits auf dem System vorhanden sind, sodass ein „Hello World“ in C++ klein sein kann, selbst wenn es eine große Menge an iostream-Code aus der C++-Standardbibliothek verwendet. Außerdem ist die Codegröße vielleicht ein Faktor, der die Kaltstartzeiten beeinflusst, da mehr Code länger zum Laden von der Festplatte benötigt, aber die Kosten sind im Allgemeinen nicht signifikant, und moderne Betriebssysteme mildern dies auf verschiedene Weise ab, z. B. durch das Cachen von Apps, deren Laden erwartet wird.
Im Fall von Emscripten wird der Code typischerweise im Web ausgeführt. Das bedeutet folgendes:
Die Anwendung läuft in einer Sandbox. Sie hat keine lokalen Systembibliotheken, mit denen sie dynamisch verlinkt werden kann; sie muss ihren eigenen Systembibliothek-Code mitliefern.
Die Codegröße ist ein großes Problem, da der Code der Anwendung über das Internet heruntergeladen wird, was um viele Größenordnungen langsamer ist als eine installierte native App auf dem lokalen Rechner.
Aus diesem Grund verwaltet Emscripten Systembibliotheken automatisch für Sie und führt automatisch eine Eliminierung von totem Code (Dead Code Elimination) usw. durch, um sie so klein wie möglich zu halten.
Ein weiterer Faktor ist, dass Emscripten „js-libraries“ besitzt – Systembibliotheken, die in JavaScript geschrieben sind. Über solche Systembibliotheken greifen wir auf APIs im Web zu. Es ist auch ein bequemer Weg für Entwickler, kompilierten Code und handgeschriebenen Code auf derselben Seite zu verbinden. Dies ist ein weiterer Grund für Emscripten, Systembibliotheken auf eine spezielle Weise zu behandeln, insbesondere auf eine Weise, die es ermöglicht, so viel wie möglich von diesen js-Bibliotheken zu entfernen, sodass nur das übrig bleibt, was tatsächlich verwendet wird. Auch dies funktioniert am besten im Kontext des statischen Linkens einer eigenständigen App ohne externe Abhängigkeiten.
Das dynamische Linken in Emscripten ist recht einfach: Sie erstellen mehrere separate Code-„Module“ aus Ihrem Quellcode und können diese zur Laufzeit verlinken. Das Linken verbindet im Grunde die undefinierten Symbole in jedem Modul mit den definierten Symbolen in den anderen auf einfachste Weise. Es unterstützt derzeit keine speziellen Grenzfälle.
Systembibliotheken nutzen jedoch einige fortgeschrittenere Linking-Funktionen, die solche Grenzfälle beinhalten. Aus diesem Grund versucht Emscripten, das Problem wie folgt zu vereinfachen: Es gibt zwei Arten von Shared-Modulen:
Main-Module (Hauptmodule), in die Systembibliotheken einverlinkt sind.
Side-Module (Nebenmodule), in die keine Systembibliotheken einverlinkt sind.
Ein Projekt sollte genau ein Main-Modul enthalten. Dieses kann dann zur Laufzeit mit mehreren Side-Modulen verlinkt werden. Dieses Modell vereinfacht auch andere Dinge. Beispielsweise enthält nur das Singleton-Main-Modul die JavaScript-Umgebung, während Side-Module reine WebAssembly-Module sind.
Der einzige knifflige Aspekt dieses Designs ist, dass ein Side-Modul von einer Systembibliothek abhängen könnte, von der das Main-Modul nicht abhängt. Siehe den Abschnitt über Systembibliotheken unten, wie man damit umgeht.
Beachten Sie, dass das „Main-Modul“ nicht zwangsläufig die main()-Funktion enthalten muss. Diese könnte genauso gut in einem Side-Modul liegen. Was das Main-Modul zum „Haupt“-Modul macht, ist, dass es nur eines davon gibt und nur in dieses Systembibliotheken einverlinkt sind.
(Beachten Sie, dass Systembibliotheken in das Main-Modul statisch einverlinkt werden. Wir profitieren immer noch von einigen Optimierungen durch diese Vorgehensweise, selbst wenn wir toten Code nicht so gut eliminieren können, wie wir es gerne würden.)
Wenn Sie direkt laufenden Code sehen möchten, können Sie in die Test-Suite schauen. Es gibt test_dylink_*-Tests, die dynamisches Linken im Allgemeinen testen, und test_dlfcn_*-Tests, die speziell dlopen() testen. Ansonsten beschreiben wir nun das Verfahren.
Dynamisches Linken zur Ladezeit bezieht sich auf den Fall, in dem die Side-Module zusammen mit dem Main-Modul während des Starts geladen werden und miteinander verlinkt werden, bevor Ihre Anwendung mit der Ausführung beginnt.
Erstellen Sie einen Teil Ihres Codes als Main-Modul und verlinken Sie ihn mit -sMAIN_MODULE.
Erstellen Sie andere Teile Ihres Codes als Side-Module und verlinken Sie diese mit -sSIDE_MODULE.
Für das Main-Modul sollte die Dateiendung .js sein (die WebAssembly-Datei wird wie gewohnt daneben generiert). Für das Side-Modul ist die Ausgabe lediglich ein WebAssembly-Modul; wir empfehlen die Dateiendung .wasm oder .so (die Endung für Shared Libraries in UNIX-Systemen).
Damit die Side-Module beim Start geladen werden, müssen Sie dem Main-Modul deren Existenz mitteilen. Dies können Sie tun, indem Sie sie beim Verlinken des Main-Moduls auf der Kommandozeile angeben, z. B.
emcc -sMAIN_MODULE main.c libsomething.wasm
Zur Laufzeit lädt der JavaScript-Ladecode libsomthing.wasm (zusammen mit allen anderen Side-Modulen) und das Main-Modul, bevor die Anwendung startet. Die laufende Anwendung kann dann auf Code aus allen miteinander verlinkten Modulen zugreifen.
dlopen()¶Dynamisches Linken zur Laufzeit kann durch Aufruf der Funktion dlopen() durchgeführt werden, um Side-Module zu laden, nachdem das Programm bereits läuft. Das Verfahren beginnt auf die gleiche Weise mit denselben Flags zum Erstellen der Main- und Side-Module. Der Unterschied besteht darin, dass Sie die Side-Module beim Verlinken des Main-Moduls nicht auf der Kommandozeile angeben; stattdessen müssen Sie das Side-Modul in das Dateisystem laden, damit dlopen (oder fopen usw.) darauf zugreifen kann (außer für dlopen(NULL), was das Öffnen des aktuellen Executables bedeutet und ohne Dateisystem-Integration funktioniert). Das ist im Grunde alles – Sie können dann dlopen(), dlsym() usw. ganz normal verwenden.
Standardmäßig deaktivieren Main-Module die Dead Code Elimination. Das bedeutet, dass der gesamte kompilierte Code in der Ausgabe verbleibt, einschließlich aller einverlinkten Systembibliotheken und des gesamten JS-Bibliothekscodes.
Dies ist das Standardverhalten, da es am wenigsten überraschend ist. Es ist jedoch auch möglich, die normale Dead Code Elimination zu verwenden, indem man mit -sMAIN_MODULE=2 (statt 1) baut. In diesem Modus wird das Main-Modul normal gebaut, ohne spezielles Verhalten, um Code am Leben zu erhalten. Es liegt dann in Ihrer Verantwortung sicherzustellen, dass Code, den Side-Module benötigen, erhalten bleibt. Dies können Sie entweder durch Hinzufügen zu EXPORTED_FUNCTIONS oder durch Markieren des Symbols mit EMSCRIPTEN_KEEPALIVE im Quellcode tun. Siehe other.test_minimal_dynamic für ein Beispiel dazu in Aktion.
Wenn Sie dynamisches Linken zur Ladezeit durchführen, werden alle Symbole, die von den auf der Kommandozeile angegebenen Side-Modulen benötigt werden, automatisch am Leben erhalten. Aus diesem Grund empfehlen wir dringend die Verwendung von MAIN_MODULE=2 beim dynamischen Linken zur Ladezeit.
Es gibt auch das entsprechende -sSIDE_MODULE=2 für Side-Module.
Wie bereits erwähnt, werden Systembibliotheken vom Emscripten-Linker auf besondere Weise behandelt, und beim dynamischen Linken wird nur das Main-Modul gegen Systembibliotheken verlinkt. Beim Verlinken des Main-Moduls ist es möglich, die Side-Module auf der Kommandozeile zu übergeben, wodurch alle Systembibliotheks-Abhängigkeiten automatisch behandelt werden.
Wenn jedoch ein Main-Modul ohne seine Side-Module verlinkt wird (normalerweise mit -sMAIN_MODULE=1), kann es sein, dass erforderliche Systembibliotheken nicht enthalten sind. Dieser Abschnitt erklärt, was zu tun ist, um dies zu beheben, indem man das Main-Modul zwingt, gegen bestimmte Bibliotheken zu verlinken.
Sie können das Main-Modul mit EMCC_FORCE_STDLIBS=1 in der Umgebung bauen, um die Einbeziehung aller Standardbibliotheken zu erzwingen. Ein verfeinerter Ansatz besteht darin, die Systembibliotheken zu benennen, die Sie explizit einbeziehen möchten. Zum Beispiel mit so etwas wie EMCC_FORCE_STDLIBS=libcxx,libcxxabi (falls Sie diese beiden Bibliotheken benötigen).
Native Linker führen Code im Allgemeinen nur aus, wenn alle Symbole aufgelöst sind. Emscriptens dynamischer Linker verbindet Symbole mit nicht aufgelösten Referenzen dynamisch. Infolgedessen prüfen wir nicht, ob Symbole nicht aufgelöst bleiben, und der Code kann mit der Ausführung beginnen, selbst wenn dies der Fall ist. Er wird erfolgreich laufen, wenn sie in der Praxis nicht aufgerufen werden. Wenn sie doch aufgerufen werden, erhalten Sie einen Laufzeitfehler. Was schiefgelaufen ist, sollte aus dem Stack-Trace (in einem nicht-minifizierten Build) ersichtlich sein; das Bauen mit -sASSERTIONS kann hier weiterhelfen.
Chromium unterstützt das Kompilieren von >4kB WASM auf dem Haupt-Thread nicht, und das schließt Side-Module ein; Sie können --use-preload-plugins (in emcc oder file_packager.py) verwenden, um Emscripten anzuweisen, diese beim Start zu kompilieren [doc] [Diskussion].
EM_ASM und EM_JS Code, der innerhalb von Side-Modulen definiert ist, hängt von eval-Unterstützung ab und ist daher inkompatibel mit -sDYNAMIC_EXECUTION=0.
Dynamisches Linken + Pthreads ist noch experimentell. Daher wird das Verlinken mit sowohl MAIN_MODULE als auch -pthread eine Warnung erzeugen.
Während dynamisches Linken zur Ladezeit ohne Komplikationen funktioniert, kann das dynamische Linken zur Laufzeit über dlopen/dlsym zusätzliche Überlegungen erfordern. Der Grund dafür ist, dass das Synchronhalten der Indirektions-Funktionszeigertabelle zwischen den Threads durch Emscripten-Bibliothekscode erfolgen muss. Jedes Mal, wenn eine neue Bibliothek geladen oder ein neues Symbol über dlsym angefordert wird, können Tabellenplätze hinzugefügt werden, und diese Änderungen müssen auf jedem Thread im Prozess gespiegelt werden.
Änderungen an der Tabelle sind durch einen Mutex geschützt, und bevor ein Thread von dlopen oder dlsym zurückkehrt, wartet er, bis alle anderen Threads synchronisiert sind. Um diese Synchronisation so nahtlos wie möglich zu gestalten, klinken wir uns in die Low-Level-Primitive von emscripten_futex_wait und emscirpten_yield ein.
In den meisten Anwendungsfällen geschieht dies alles im Hintergrund und es ist keine besondere Aktion erforderlich. Es gibt jedoch eine Klasse von Anwendungen, die derzeit Änderungen erfordern könnte. Wenn Ihre Anwendung Busy-Waiting betreibt oder direkt die atomic.waitXX Instruktionen (oder die clang __builtin_wasm_memory_atomic_waitXX Builtins) verwendet, müssen Sie sie möglicherweise auf emscripten_futex_wait umstellen, um Deadlocks zu vermeiden. Wenn Sie emscripten_futex_wait nicht verwenden, während Sie blockieren, könnten Sie potenziell andere Threads blockieren, die dlopen und/oder dlsym aufrufen.