Qemu.js mat JIT-Ënnerstëtzung: Dir kënnt ëmmer nach d'Mënz réckelen

Virun e puer Joer Fabrice Bellard geschriwwen vum jslinux ass e PC Emulator geschriwwen a JavaScript. Duerno gouf et op d'mannst méi Virtuell x86. Awer all vun hinnen, souwäit ech weess, waren Dolmetscher, während Qemu, vill virdrun vum selwechte Fabrice Bellard geschriwwen, an, wahrscheinlech, all selbst respektéierende modernen Emulator, benotzt JIT Kompiléierung vum Gaaschtcode an Hostsystemcode. Et huet mir geschéngt datt et Zäit wier déi entgéintgesate Aufgab am Zesummenhang mat deem ëmzesetzen, deen d'Browser léisen: JIT-Kompilatioun vu Maschinncode an JavaScript, fir déi et am meeschte logesch schéngt Qemu ze portéieren. Et schéngt, firwat Qemu, et gi méi einfach a userfrëndlech Emulatoren - déiselwecht VirtualBox, zum Beispill - installéiert a funktionnéiert. Awer Qemu huet e puer interessant Features

  • oppe Quell
  • Fäegkeet ouni Kernel Chauffer ze schaffen
  • Fäegkeet am Dolmetscher Modus ze schaffen
  • Ënnerstëtzung fir eng grouss Zuel vu Host- a Gaaschtarchitekturen

Wat den drëtte Punkt ugeet, kann ech elo erklären datt et tatsächlech am TCI Modus net d'Instruktioune vum Gaaschtmaschinn selwer interpretéiert ginn, awer de Bytecode, dee vun hinnen kritt gëtt, awer dëst ännert d'Essenz net - fir ze bauen a lafen Qemu op eng nei Architektur, wann Dir Gléck bass, A C Compiler ass genuch - schreiwen e Code Generator kann ausgestallt ginn.

An elo, no zwee Joer vu gemittlechen Tëschenzäit mam Qemu Quellcode a menger Fräizäit, ass e funktionnéierende Prototyp erschéngt, an deem Dir scho ka lafen, zum Beispill Kolibri OS.

Wat ass Emscripten

Hautdesdaags si vill Compileren opgetaucht, d'Ennresultat vun deem JavaScript ass. E puer, wéi Type Script, waren ursprénglech geduecht fir de beschte Wee fir de Web ze schreiwen. Zur selwechter Zäit ass Emscripten e Wee fir existent C oder C ++ Code ze huelen an an eng Browser-liesbar Form ze kompiléieren. Op dës Säit Mir hu vill Ports vu bekannte Programmer gesammelt: heiZum Beispill kënnt Dir op PyPy kucken - iwwregens, si behaapten schonn JIT ze hunn. Tatsächlech kann net all Programm einfach kompiléiert a lafen an engem Browser - et ginn eng Zuel Eegeschaften, déi Dir awer muss ausginn, well d'Inscriptioun op der selwechter Säit seet "Emscripten ka benotzt ginn fir bal all ze kompiléieren portable C/C++ Code zu JavaScript". Dat heescht, et ginn eng Rei vun Operatiounen déi ondefinéiert Verhalen no dem Standard sinn, awer normalerweis op x86 funktionnéieren - zum Beispill, net ausgeriichtten Zougang zu Variablen, wat allgemeng op verschiddenen Architekturen verbueden ass. Am Allgemengen , Qemu ass e Cross-Plattform Programm an , ech wollt gleewen, an et enthält net scho vill ondefinéiert Verhalen - huelt et a kompiléiert, da mëscht e bësse mat JIT - an Dir sidd fäerdeg! Awer dat ass net de Fall...

Éischt Versuch

Am allgemengen sinn ech net déi éischt Persoun déi mat der Iddi kënnt fir Qemu op JavaScript ze portéieren. Et gouf eng Fro am ReactOS Forum gefrot ob dëst méiglech wier mat Emscripten. Och virdru goufen et Rumeuren datt de Fabrice Bellard dat perséinlech gemaach huet, awer mir hunn iwwer jslinux geschwat, wat, souwäit ech weess, just e Versuch ass fir manuell genuch Leeschtung am JS z'erreechen, a gouf vun Null geschriwwe. Méi spéit gouf Virtual x86 geschriwwe - onobfuscéiert Quelle goufen dofir gepost, a wéi gesot, de gréissere "Realismus" vun der Emulatioun huet et méiglech SeaBIOS als Firmware ze benotzen. Zousätzlech, war et op d'mannst ee Versuch engem port Qemu benotzt Emscripten - ech probéiert dëst ze maachen Socketpair, mee Entwécklung, souwäit ech verstinn, war gefruer.

Also, et schéngt, hei sinn d'Quellen, hei ass Emscripten - huelt et a kompiléiert. Awer et ginn och Bibliothéike vun deenen de Qemu ofhängeg ass, a Bibliothéike vun deenen dës Bibliothéiken ofhängeg sinn, etc., an eng vun hinnen ass libff, op déi glib hänkt. Et goufen Rumeuren um Internet datt et een an der grousser Sammlung vu Ports vu Bibliothéiken fir Emscripten wier, awer et war iergendwéi schwéier ze gleewen: éischtens war et net geduecht fir en neie Compiler ze sinn, zweetens war et ze nidderegen Niveau. Bibliothéik fir just opzehuelen, a kompiléieren op JS. An et ass net nëmmen eng Saach vu Montage-Inserts - wahrscheinlech, wann Dir et verdréit, fir e puer Uruffkonventiounen kënnt Dir déi néideg Argumenter um Stack generéieren an d'Funktioun ouni se ruffen. Awer Emscripten ass eng komplizéiert Saach: fir de generéierte Code dem Browser JS Engine Optimizer vertraut ze maachen, ginn e puer Tricks benotzt. Besonnesch de sougenannte Relooping - e Code-Generator deen de empfaangen LLVM IR mat e puer abstrakte Iwwergangsinstruktiounen benotzt, probéiert plausibel Ifs, Loops, etc. Gutt, wéi ginn d'Argumenter un d'Funktioun weiderginn? Natierlech, als Argumenter fir JS Funktiounen, dat ass, wa méiglech, net duerch de Stack.

Am Ufank war et eng Iddi fir einfach en Ersatz fir libffi mat JS ze schreiwen an Standardtester auszeféieren, awer um Enn sinn ech duerchernee ginn wéi ech meng Headerdateien maache fir datt se mam existente Code funktionnéieren - wat kann ech maachen, wéi se soen, "Sinn d'Aufgaben esou komplex "Si mir sou domm?" Ech hat libffi zu enger anerer Architektur port, souzesoen - glécklecherweis, Emscripten huet souwuel macros fir inline Assemblée (am Javascript, jo - gutt, egal d'Architektur, also de assembler), an d'Fähegkeet Code ze lafen generéiert op der fléien. Am Allgemengen, nodeems ech eng Zäit laang mat Plattform-ofhängege libffi Fragmenter geknackt hunn, krut ech e kompiléierbare Code an hunn en op den éischten Test gelaf, deen ech begéint hunn. Zu menger Iwwerraschung war den Test erfollegräich. Erstaunlech vu mengem Genie - kee Witz, et huet vum éischte Start geschafft - ech, ëmmer nach net meng Ae gegleeft, sinn de resultéierende Code nach eng Kéier kucken, fir ze evaluéieren wou ech nächste gräifen. Hei sinn ech fir d'zweete Kéier Nëss gaang - dat eenzegt wat meng Funktioun gemaach huet war ffi_call - dëst gemellt engem erfollegräichen Opruff. Et war keen Opruff selwer. Also hunn ech meng éischt Pull-Ufro geschéckt, déi e Feeler am Test korrigéiert huet, dee fir all Olympiadstudent kloer ass - reell Zuele sollen net verglach ginn als a == b an esouguer wéi a - b < EPS - Dir musst och de Modul erënneren, soss gëtt 0 ganz vill gläich wéi 1/3 ... Am Allgemengen sinn ech mat engem gewësse Port vu libffi komm, deen déi einfachsten Tester passéiert a mat deem glib ass kompiléiert - Ech hu beschloss datt et néideg wier, ech addéieren et spéider. Wann ech no vir kucken, wäert ech soen datt, wéi et sech erausstellt, de Compiler net emol d'libffi Funktioun am leschte Code enthält.

Awer, wéi ech scho gesot hunn, ginn et e puer Aschränkungen, an ënner der gratis Notzung vu verschiddenen ondefinéierte Verhalen ass eng méi désagréabel Feature verstoppt ginn - JavaScript vum Design ënnerstëtzt net Multithreading mat gemeinsame Gedächtnis. Am Prinzip kann dëst normalerweis souguer eng gutt Iddi genannt ginn, awer net fir Porting Code deem seng Architektur un C Threads gebonnen ass. Am Allgemengen experimentéiert Firefox mat Ënnerstëtzung vun gemeinsamen Aarbechter, an Emscripten huet eng pthread-Implementatioun fir si, awer ech wollt net dovun ofhänken. Ech hu misse lues Multithreading aus dem Qemu Code rooten - dat ass, erauszefannen, wou d'Threads lafen, de Kierper vun der Loop, déi an dësem Thread leeft, an eng separat Funktioun réckelen an esou Funktiounen een nom aneren aus der Haaptschleife ruffen.

Zweeten Versuch

Irgendwann gouf et kloer datt de Problem nach ëmmer do war, an datt zoufälleg Krut ronderëm de Code drécke géif net zu engem Gutt féieren. Fazit: Mir mussen iergendwéi de Prozess vun der Crèche derbäisetzen systematiséieren. Dofir gouf d'Versioun 2.4.1, déi deemools frësch war, geholl (net 2.5.0, well, wee weess, et ginn Käfere an der neier Versioun, déi nach net gefaange sinn, an ech hu genuch vu menge Bugs. ), an déi éischt Saach war et sécher ëmzeschreiwen thread-posix.c. Gutt, dat ass, sou sécher: wann een probéiert eng Operatioun ze maachen, déi zu Blockéierung féiert, gouf d'Funktioun direkt genannt abort() - natierlech huet dëst net all d'Problemer op eemol geléist, awer op d'mannst war et iergendwéi méi agreabel wéi roueg onkonsequent Donnéeën ze kréien.

Am Allgemengen, Emscripten Optiounen sinn ganz hëllefräich am porting Code zu JS -s ASSERTIONS=1 -s SAFE_HEAP=1 - si fangen e puer Aarte vun ondefinéiert Verhalen, sou wéi Uruff un eng net ausgeriicht Adress (wat guer net konsequent ass mam Code fir getippten Arrays wéi HEAP32[addr >> 2] = 1) oder eng Funktioun mat der falscher Unzuel vun Argumenter opruffen.

Iwwregens, Ausriichtungsfehler sinn en separaten Thema. Wéi ech scho gesot hunn, huet de Qemu en "degeneréierten" interpretative Backend fir Code Generatioun TCI (klenge Code Dolmetscher), a fir Qemu op enger neier Architektur ze bauen an ze lafen, wann Dir Gléck hutt, ass e C Compiler genuch. "wann Dir Gléck hutt". Ech hat Pech, an et huet sech erausgestallt datt TCI unalignéierten Zougang benotzt wann se säi Bytecode parséieren. Dat ass, op all Zorte vun ARM an aner Architektur mat onbedéngt Niveau Zougang, Qemu kompiléiert well se eng normal TCG Backend hunn dass gebierteg Code generéiert, mee ob TCI op hinnen Aarbecht ass eng aner Fro. Wéi et sech awer erausstellt, huet d'TCI Dokumentatioun kloer eppes ähnleches uginn. Als Resultat, Funktioun rifft fir unaligned liesen goufen Code dobäi, déi an engem aneren Deel vun Qemu entdeckt goufen.

Koup Zerstéierung

Als Resultat gouf den unalignéierten Zougang zu TCI korrigéiert, eng Haaptschleife gouf erstallt, déi am Tour de Prozessor, RCU an e puer aner kleng Saachen genannt huet. An dofir starten ech Qemu mat der Optioun -d exec,in_asm,out_asm, dat heescht datt Dir musst soen, wéi eng Blocks vum Code ausgefouert ginn, an och zum Zäitpunkt vun der Sendung fir ze schreiwen wat de Gaaschtcode war, wat de Hostcode gouf (an dësem Fall Bytecode). Et fänkt un, féiert e puer Iwwersetzungsblocken aus, schreift d'Debugging Message, déi ech hannerlooss hunn, datt RCU elo fänkt un ... abort() bannent enger Funktioun free(). Andeems Dir mat der Funktioun ze manipuléieren free() Mir hunn et fäerdeg bruecht erauszefannen datt am Header vum Heapblock, deen an den aacht Bytes virun der zougewisener Erënnerung läit, amplaz vun der Blockgréisst oder eppes ähnleches, Dreck war.

Zerstéierung vum Koup - wéi léif ... An esou engem Fall gëtt et eng nëtzlech Recours - aus (wa méiglech) déiselwecht Quellen, sammelen eng gebierteg Binär a lafen se ënner Valgrind. No enger Zäit war de Binär fäerdeg. Ech lancéieren et mat deene selwechte Optiounen - et crasht och während der Initialiséierung, ier se tatsächlech d'Ausféierung erreecht hunn. Et ass onsympathesch, natierlech - anscheinend waren d'Quellen net genee déiselwecht, wat net iwwerraschend ass, well d'Konfiguratioun liicht verschidden Optiounen ausgeschwat huet, awer ech hunn Valgrind - als éischt wäert ech dëse Feeler fixen, an dann, wann ech Gléck hunn , erschéngt d'Original. Ech laafen déi selwecht Saach ënner Valgrind ... Y-y-y, y-y-y, äh-äh, et huet ugefaang, duerch d'Initialiséierung normalerweis gaang an ass laanscht den urspréngleche Käfer geplënnert ouni eng eenzeg Warnung iwwer falsch Erënnerungszougang, net iwwer Falen ze ernimmen. D'Liewen, wéi se soen, huet mech net op dëst virbereet - e Crashprogramm hält op wann se ënner Walgrind gestart ginn. Wat et war ass e Geheimnis. Meng Hypothese ass datt eemol an der Géigend vun der aktueller Instruktioun no engem Crash während der Initialiséierung, gdb Aarbecht gewisen huet memset-a mat engem gëltege Pointer benotzt entweder mmx, oder xmm registréiert, da war et vläicht eng Zort Ausriichtungsfehler, obwuel et nach ëmmer schwéier ass ze gleewen.

Okay, Valgrind schéngt hei net ze hëllefen. An hei huet déi ekelst Saach ugefaang - alles schéngt souguer unzefänken, awer crasht aus absolut onbekannte Grënn wéinst engem Event dat viru Millioune vun Instruktioune geschéie kéint. Laang war et emol net kloer, wéi et soll ugoen. Um Enn hunn ech nach ëmmer misse sëtzen an debuggen. D'Dréckerei mat deem den Header ëmgeschriwwe gouf, huet gewisen datt et net wéi eng Nummer ausgesäit, mee éischter eng Aart vu binären Donnéeën. A kuck, dës binär String gouf an der BIOS-Datei fonnt - dat ass, elo war et méiglech mat raisonnabel Vertrauen ze soen datt et e Pufferiwwerfluss war, an et ass souguer kloer datt et op dëse Puffer geschriwwe gouf. Gutt, dann eppes wéi dëst - an Emscripten, glécklecherweis, gëtt et keng randomization vun der Adress Plaz, et sinn och keng Lächer an et, also du kanns iergendwou an der Mëtt vum Code schreiwen fir Daten duerch Zeigefanger aus dem leschte Start erausginn, kuckt op d'Donnéeën, kuckt op de Pointer, an, wann et net geännert huet, kritt Nahrung fir Gedanken. Richteg, et dauert e puer Minutten fir no all Ännerung ze verbannen, awer wat kënnt Dir maachen? Als Resultat gouf eng spezifesch Linn fonnt, déi de BIOS vum temporäre Puffer an d'Gaaschtminne kopéiert huet - an tatsächlech war et net genuch Plaz am Puffer. D'Quell vun där komescher Pufferadress ze fannen huet zu enger Funktioun gefouert qemu_anon_ram_alloc am Fichier oslib-posix.c - d'Logik do war dëst: heiansdo kann et nëtzlech sinn d'Adress op eng rieseg Säit vun 2 MB an der Gréisst ze alignéieren, dofir wäerte mir froen mmap éischt e bësse méi, an dann wäerte mir d'iwwerschësseg mat der Hëllef zréck munmap. A wann esou Ausrichtung net erfuerderlech ass, da wäerte mir d'Resultat uginn anstatt 2 MB getpagesize() - mmap et wäert nach ëmmer eng ausgeriicht Adress ginn ... Also am Emscripten mmap just rifft malloc, mee natierlech alignéiert et net op der Säit. Am Allgemengen, e Feeler, dee mech fir e puer Méint frustréiert huet, gouf duerch eng Ännerung korrigéiert zwee Linnen.

Fonctiounen vun Opruff Funktiounen

An elo zielt de Prozessor eppes, Qemu crasht net, awer den Ecran schalt net un, an de Prozessor geet séier an d'Schleifen, no der Ausgang beurteelen -d exec,in_asm,out_asm. Eng Hypothese ass entstanen: Timer Ënnerbriechungen (oder, allgemeng, all Ënnerbriechungen) kommen net. An tatsächlech, wann Dir d'Ënnerbriechunge vun der gebierteg Versammlung ofschrauwen, déi aus irgendege Grënn geschafft huet, kritt Dir en ähnlecht Bild. Awer dëst war guer net d'Äntwert: e Verglach vun de Spuren, déi mat der uewe genannter Optioun erausginn hunn, huet gewisen datt d'Ausféierungsbunnen ganz fréi divergéiert hunn. Hei muss gesot ginn, datt de Verglach vun deem wat opgeholl gouf mam Launcher emrun Debugging Output mam Output vun der gebierteg Versammlung ass net e komplett mechanesche Prozess. Ech weess net genau wéi e Programm, deen an engem Browser leeft, verbënnt emrun, awer e puer Zeilen am Ausgang weisen sech ëmarrangéiert, sou datt den Ënnerscheed am Diff nach kee Grond ass fir unzehuelen datt d'Trajectoiren divergéiert sinn. Am Allgemengen, gouf et kloer, datt no den Instruktioune ljmpl et gëtt en Iwwergank op verschidden Adressen, an de Bytecode generéiert ass grondsätzlech anescht: een enthält eng Instruktioun fir eng Helperfunktioun ze ruffen, deen aneren net. Nodeems Dir d'Instruktioune gegoogelt hutt an de Code studéiert deen dës Instruktiounen iwwersetzt, gouf et kloer datt éischtens direkt virun et am Register cr0 eng Opnam gouf gemaach - och mat Hëllef vun engem Helfer - deen de Prozessor an de geschützte Modus gewiesselt huet, an zweetens datt d'js Versioun ni op de geschützte Modus gewiesselt ass. Awer d'Tatsaach ass datt eng aner Feature vun Emscripten seng Verzweifelung ass fir Code ze toleréieren wéi d'Ëmsetzung vun Instruktiounen call an TCI, wat all Funktiounszeiger am Typ resultéiert long long f(int arg0, .. int arg9) - Funktiounen musse mat der korrekter Unzuel vun Argumenter genannt ginn. Wann dës Regel verletzt gëtt, ofhängeg vun den Debugging-Astellungen, wäert de Programm entweder Crash (wat gutt ass) oder iwwerhaapt déi falsch Funktioun nennen (wat traureg ass ze Debuggen). Et gëtt och eng drëtt Optioun - aktivéiert d'Generatioun vu Wrapper déi Argumenter addéieren / ewechhuelen, awer am Ganzen huelen dës Wrapper vill Plaz op, trotz der Tatsaach datt ech tatsächlech nëmmen e bësse méi wéi honnert Wrapper brauch. Dëst eleng ass ganz traureg, awer et huet sech e méi eeschte Problem erausgestallt: am generéierte Code vun de Wrapperfunktiounen goufen d'Argumenter ëmgewandelt an ëmgewandelt, awer heiansdo gouf d'Funktioun mat den generéierten Argumenter net genannt - gutt, grad wéi an meng libffi Ëmsetzung. Dat ass, e puer Helfer goufen einfach net ausgefouert.

Glécklecherweis huet Qemu Maschinn liesbar Lëschte vun Helfer a Form vun enger Header Datei wéi

DEF_HELPER_0(lock, void)
DEF_HELPER_0(unlock, void)
DEF_HELPER_3(write_eflags, void, env, tl, i32)

Si ginn zimlech witzeg benotzt: als éischt ginn Makroen op déi komeschst Manéier nei definéiert DEF_HELPER_n, an dann schalt op helper.h. Am Ausmooss datt de Makro an e Strukturinitialisator an e Komma erweidert gëtt, an dann ass eng Array definéiert, an amplaz vun Elementer - #include <helper.h> Als Resultat hat ech endlech eng Chance d'Bibliothéik op der Aarbecht ze probéieren pyparséieren, an e Skript gouf geschriwwen, deen genee dës Wrapper generéiert fir genee déi Funktiounen, fir déi se gebraucht ginn.

An esou, duerno schéngt de Prozessor ze schaffen. Et schéngt ze sinn well den Ecran ni initialiséiert gouf, obwuel memtest86+ konnt an der gebierteg Versammlung lafen. Hei ass et néideg ze klären datt de Qemu Block I / O Code a Coroutines geschriwwe gëtt. Emscripten huet seng eege ganz komplizéiert Ëmsetzung, awer et muss nach ëmmer am Qemu Code ënnerstëtzt ginn, an Dir kënnt de Prozessor elo debuggen: Qemu ënnerstëtzt Optiounen -kernel, -initrd, -append, mat deem Dir Linux oder zum Beispill memtest86+ boote kënnt, ouni iwwerhaapt Blockapparaten ze benotzen. Awer hei ass de Problem: an der gebierteg Versammlung konnt een de Linux Kernel Output op d'Konsole mat der Optioun gesinn -nographic, a keng Ausgang vum Browser op den Terminal vu wou et lancéiert gouf emrun, koum net. Dat ass, et ass net kloer: de Prozessor funktionnéiert net oder d'Grafikoutput funktionnéiert net. An dunn ass et mir komm, e bëssen ze waarden. Et huet sech erausgestallt datt "de Prozessor net schléift, awer einfach lues blénkt", an no ongeféier fënnef Minutten huet de Kernel eng Rëtsch Messagen op d'Konsole geheit a weider hänke gelooss. Et gouf kloer datt de Prozessor am Allgemengen funktionnéiert, a mir mussen an de Code gräifen fir mat SDL2 ze schaffen. Leider weess ech net wéi ech dës Bibliothéik benotzen, also op e puer Plazen hunn ech missen zoufälleg handelen. Irgendwann huet d'Linn parallel0 um Écran op engem bloen Hannergrond geblitzt, wat e puer Gedanken proposéiert huet. Um Enn huet sech erausgestallt datt de Problem war datt Qemu e puer virtuelle Fënsteren an enger kierperlecher Fënster opmaacht, tëscht deenen Dir mat Ctrl-Alt-n wiessele kënnt: et funktionnéiert am gebiertege Bau, awer net an Emscripten. Nodeems Dir onnéideg Fënstere mat Optiounen lass kritt -monitor none -parallel none -serial none an Instruktioune fir de ganze Bildschierm op all Frame mat Kraaft ze zéien, alles huet op eemol geschafft.

Coroutinen

Also, Emulatioun am Browser funktionnéiert, awer Dir kënnt näischt interessant Single-Floppy dran lafen, well et kee Block I / O ass - Dir musst Ënnerstëtzung fir Coroutines implementéieren. Qemu huet scho verschidde Coroutine-Backends, awer wéinst der Natur vum JavaScript an dem Emscripten Code Generator, kënnt Dir net nëmmen mat Stacks jongléieren. Et géif schéngen datt "alles fort ass, de Putz gëtt ewechgeholl", awer d'Emscripten-Entwéckler hunn sech schonn ëm alles gekëmmert. Dëst ass zimlech witzeg ëmgesat: loosst eis e Funktiounsruff wéi dësen verdächteg nennen emscripten_sleep an e puer anerer déi den Asyncify Mechanismus benotzen, wéi och Zeigefanger a rifft op all Funktioun, wou ee vun de fréiere zwee Fäll méi wäit um Stack ka geschéien. An elo, virun all verdächtegen Uruff, wäerte mir en asynchrone Kontext auswielen, an direkt nom Uruff kucken mir ob en asynchronen Uruff geschitt ass, a wann et geschitt ass, späichere mir all lokal Variablen an dësem asynchrone Kontext, uginn wéi eng Funktioun fir d'Kontroll ze transferéieren wann mir d'Ausféierung weiderfuere mussen, an déi aktuell Funktioun ausgoen. Dëst ass wou et Spillraum ass fir den Effekt ze studéieren verschwenden - fir d'Bedierfnesser vun der weiderer Code Ausféierung nodeems se vun engem asynchronen Uruff zréckkoum, generéiert de Compiler "Stubs" vun der Funktioun, déi no engem verdächtegen Uruff starten - sou: wann et n verdächteg Uriff ginn, da gëtt d'Funktioun iergendwou n/2 erweidert Zäiten - dëst ass ëmmer nach, wann net. Denkt drun datt no all potenziell asynchronen Uruff Dir e puer lokal Variabelen un d'Originalfunktioun spuere musst. Duerno hunn ech souguer en einfacht Skript am Python missen schreiwen, deen, baséiert op engem bestëmmte Set vu besonnesch iwwerbenotzten Funktiounen, déi vermeintlech "net d'Asynchronie duerch sech selwer passéiere loossen" (dat ass, Stack Promotioun an alles wat ech just beschriwwen hunn net schafft an hinnen), weist Uriff duerch Pointer un, an deenen d'Funktioune vum Compiler ignoréiert solle ginn, sou datt dës Funktiounen net als asynchron ugesi ginn. An dann sinn JS Dateien ënner 60 MB kloer ze vill - loosst eis op d'mannst soen 30. Och wann ech eemol e Versammlungsskript opgeriicht hunn an zoufälleg d'Linkeroptiounen erausgehäit hunn, dorënner och -O3. Ech lafen de generéierte Code, a Chromium ësst Erënnerung a kraazt. Ech hunn dunn zoufälleg gekuckt wat hie probéiert erofzelueden ... Gutt, wat kann ech soen, ech hätt och gefruer wann ech gefrot gi wier, e 500+ MB Javascript nodenklech ze studéieren an ze optimiséieren.

Leider waren d'Schecken am Asyncify Support Bibliothéik Code net ganz frëndlech mat longjmp-s déi am virtuelle Prozessorcode benotzt ginn, awer no engem klenge Patch, deen dës Kontrollen deaktivéiert a Kraaft restauréiert Kontexter wéi wann alles gutt wier, huet de Code geschafft. An dunn huet eng komesch Saach ugefaang: heiansdo goufen Kontrollen am Synchroniséierungscode ausgeléist - déiselwecht déi de Code ofbriechen, wann en no der Ausféierungslogik gespaart sollt ginn - een huet probéiert e scho gefaange Mutex ze gräifen. Glécklecherweis huet dëst sech net e logesche Problem am serialiséierte Code erausgestallt - ech hunn einfach d'Standard Haaptschleiffunktionalitéit benotzt, déi vun Emscripten zur Verfügung gestallt gëtt, awer heiansdo géif den asynchronen Uruff de Stack komplett auswéckelen, an dee Moment géif et falen setTimeout vun der Haaptschleife - also ass de Code an d'Haaptschleif-Iteratioun agaangen ouni déi viregt Iteratioun ze verloossen. Rewrited op eng onendlech Loop an emscripten_sleep, an d'Problemer mat mutexes gestoppt. De Code ass souguer méi logesch ginn - schliisslech, ech hu kee Code deen den nächsten Animatiounsframe virbereet - de Prozessor berechent just eppes an den Ecran gëtt periodesch aktualiséiert. Allerdéngs hunn d'Problemer net do opgehalen: heiansdo géif d'Qemu Ausféierung einfach roueg ophalen ouni Ausnahmen oder Feeler. Dee Moment hunn ech et opginn, awer no vir kucken, wäert ech soen datt de Problem dëst war: de Coroutine Code benotzt tatsächlech net setTimeout (oder op d'mannst net esou oft wéi Dir mengt vläicht): Funktioun emscripten_yield setzt einfach den asynchronen Opruff Fändel. De ganze Punkt ass dat emscripten_coroutine_next ass keng asynchron Funktioun: intern iwwerpréift se de Fändel, setzt se zréck an iwwerdréit d'Kontroll op wou et gebraucht gëtt. Dat ass, d'Promotioun vum Stack endet do. De Problem war datt wéinst der Notzung-After-Free, déi erschéngt wann de Coroutine Pool deaktivéiert gouf wéinst der Tatsaach datt ech keng wichteg Zeil vum Code aus dem existente Coroutine Backend kopéiert hunn, d'Funktioun qemu_in_coroutine richteg zréckginn wann et tatsächlech falsch sollt zréckkommen. Dëst huet zu engem Opruff gefouert emscripten_yield, uewendriwwer war keen op de Stack emscripten_coroutine_next, de Stack huet sech ganz uewen ausgeklappt, awer nee setTimeout, wéi ech scho gesot hunn, war net ausgestallt.

JavaScript Code Generatioun

An hei ass tatsächlech de versprach "d'Gehackt zréckdréien." Net wierklech. Natierlech, wa mir Qemu am Browser lafen, an Node.js an et, dann, natierlech, no Code Generatioun an Qemu wäerte mir komplett falsch JavaScript kréien. Awer nach ëmmer eng Aart vun ëmgedréint Transformatioun.

Als éischt e bëssen iwwer wéi Qemu funktionnéiert. Weg verzeien mech direkt: Ech net eng berufflech Qemu Entwéckler a meng Conclusiounen kann op e puer Plazen falsch ginn. Wéi se soen, "D'Meenung vum Student muss net mat der Meenung vum Enseignant, dem Peano seng Axiomatik an dem gesonde Mënscheverstand zesummefalen." Qemu huet eng gewëssen Unzuel vun ënnerstëtzte Gaaschtarchitekturen a fir all gëtt et e Verzeichnis wéi target-i386. Wann Dir baut, kënnt Dir Ënnerstëtzung fir verschidde Gaaschtarchitekturen spezifizéieren, awer d'Resultat wäert nëmme verschidde Binären sinn. De Code fir d'Gaaschtarchitektur z'ënnerstëtzen, generéiert ofwiesselnd e puer intern Qemu Operatiounen, déi den TCG (Tiny Code Generator) scho a Maschinncode fir d'Hostarchitektur verwandelt. Wéi an der readme Datei am tcg Verzeichnis uginn, war dëst ursprénglech Deel vun engem normale C Compiler, dee spéider fir JIT adaptéiert gouf. Dofir, zum Beispill, Zilarchitektur am Sënn vun dësem Dokument ass net méi eng Gaaschtarchitektur, mee eng Hostarchitektur. Irgendwann ass en anere Bestanddeel opgetaucht - Tiny Code Interpreter (TCI), dee Code soll ausféieren (bal déiselwecht intern Operatiounen) an der Verontreiung vun engem Code Generator fir eng spezifesch Hostarchitektur. Tatsächlech, wéi seng Dokumentatioun seet, kann dësen Dolmetscher net ëmmer sou gutt funktionnéieren wéi e JIT Code Generator, net nëmme quantitativ a punkto Geschwindegkeet, awer och qualitativ. Och wann ech net sécher sinn datt seng Beschreiwung komplett relevant ass.

Am Ufank hunn ech probéiert e vollwäertege TCG-Backend ze maachen, awer gouf séier duercherneen am Quellcode an eng net ganz kloer Beschreiwung vun den Bytecode-Instruktiounen, also hunn ech décidéiert den TCI Dolmetscher ze wéckelen. Dëst huet e puer Virdeeler ginn:

  • Wann Dir e Code Generator implementéiert, kënnt Dir net op d'Beschreiwung vun den Instruktiounen kucken, mee op den Dolmetschercode
  • Dir kënnt Funktiounen generéieren net fir all Iwwersetzungsblock déi begéint ass, awer zum Beispill nëmmen no der Honnertsten Ausféierung
  • wann de generéierte Code ännert (an dëst schéngt méiglech ze sinn, no de Funktiounen mat Nimm beurteelen, déi d'Wuert Patch enthalen), muss ech de generéierten JS Code ongëlteg maachen, awer op d'mannst wäert ech eppes hunn fir se ze regeneréieren

Wat den drëtte Punkt ugeet, sinn ech net sécher datt Patching méiglech ass nodeems de Code fir d'éischte Kéier ausgefouert gëtt, awer déi éischt zwee Punkte si genuch.

Am Ufank gouf de Code a Form vun engem grousse Schalter op der Adress vun der ursprénglecher Bytecode-Uweisung generéiert, awer dann, erënnert un den Artikel iwwer Emscripten, Optimiséierung vun generéierten JS an Relooping, hunn ech decidéiert méi mënschleche Code ze generéieren, besonnesch well et empiresch ass. huet sech erausgestallt datt deen eenzegen Entrée an den Iwwersetzungsblock säin Start ass. Net méi séier gesot wéi gemaach, no enger Zäit hu mir e Code Generator deen Code mat Ifs generéiert (wann och ouni Schleifen). Awer Pech, et ass erofgefall, e Message ginn, datt d'Instruktioune vun enger falscher Längt waren. Ausserdeem war déi lescht Instruktioun op dësem Rekursiounsniveau brcond. Okay, ech addéieren eng identesch Scheck fir d'Generatioun vun dëser Instruktioun virun an no dem rekursive Uruff an ... net ee vun hinnen gouf ausgefouert, awer no der Assertschalter si se nach ëmmer gescheitert. Um Enn, nodeems ech de generéierte Code studéiert hunn, hunn ech gemierkt datt nom Schalter de Pointer op déi aktuell Instruktioun vum Stack nei gelueden gëtt a wahrscheinlech vum generéierten JavaScript Code iwwerschriwwe gëtt. An esou huet et sech erausgestallt. D'Erhéijung vum Puffer vun engem Megabyte op zéng huet zu näischt gefouert, an et gouf kloer datt de Code Generator a Krees leeft. Mir hu misse kontrolléieren datt mir net iwwer d'Grenze vun der aktueller TB goen, a wa mir et gemaach hunn, da gitt d'Adress vum nächste TB mat engem Minuszeechen aus, fir datt mir d'Ausféierung weiderfuere kënnen. Zousätzlech léist dëst de Problem "wat generéiert Funktiounen solle ongëlteg ginn wann dëst Stéck Bytecode geännert huet?" - nëmmen d'Funktioun, déi zu dësem Iwwersetzungsblock entsprécht, muss ongëlteg ginn. Iwwregens, obwuel ech alles am Chromium debugged (well ech Firefox benotzen an et méi einfach ass fir mech en separaten Browser fir Experimenter ze benotzen), huet Firefox mir gehollef Inkompatibilitéiten mam asm.js Standard ze korrigéieren, duerno huet de Code méi séier ugefaang ze schaffen Chrom.

Beispill vun generéiert Code

Compiling 0x15b46d0:
CompiledTB[0x015b46d0] = function(stdlib, ffi, heap) {
"use asm";
var HEAP8 = new stdlib.Int8Array(heap);
var HEAP16 = new stdlib.Int16Array(heap);
var HEAP32 = new stdlib.Int32Array(heap);
var HEAPU8 = new stdlib.Uint8Array(heap);
var HEAPU16 = new stdlib.Uint16Array(heap);
var HEAPU32 = new stdlib.Uint32Array(heap);

var dynCall_iiiiiiiiiii = ffi.dynCall_iiiiiiiiiii;
var getTempRet0 = ffi.getTempRet0;
var badAlignment = ffi.badAlignment;
var _i64Add = ffi._i64Add;
var _i64Subtract = ffi._i64Subtract;
var Math_imul = ffi.Math_imul;
var _mul_unsigned_long_long = ffi._mul_unsigned_long_long;
var execute_if_compiled = ffi.execute_if_compiled;
var getThrew = ffi.getThrew;
var abort = ffi.abort;
var qemu_ld_ub = ffi.qemu_ld_ub;
var qemu_ld_leuw = ffi.qemu_ld_leuw;
var qemu_ld_leul = ffi.qemu_ld_leul;
var qemu_ld_beuw = ffi.qemu_ld_beuw;
var qemu_ld_beul = ffi.qemu_ld_beul;
var qemu_ld_beq = ffi.qemu_ld_beq;
var qemu_ld_leq = ffi.qemu_ld_leq;
var qemu_st_b = ffi.qemu_st_b;
var qemu_st_lew = ffi.qemu_st_lew;
var qemu_st_lel = ffi.qemu_st_lel;
var qemu_st_bew = ffi.qemu_st_bew;
var qemu_st_bel = ffi.qemu_st_bel;
var qemu_st_leq = ffi.qemu_st_leq;
var qemu_st_beq = ffi.qemu_st_beq;

function tb_fun(tb_ptr, env, sp_value, depth) {
  tb_ptr = tb_ptr|0;
  env = env|0;
  sp_value = sp_value|0;
  depth = depth|0;
  var u0 = 0, u1 = 0, u2 = 0, u3 = 0, result = 0;
  var r0 = 0, r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r7 = 0, r8 = 0, r9 = 0;
  var r10 = 0, r11 = 0, r12 = 0, r13 = 0, r14 = 0, r15 = 0, r16 = 0, r17 = 0, r18 = 0, r19 = 0;
  var r20 = 0, r21 = 0, r22 = 0, r23 = 0, r24 = 0, r25 = 0, r26 = 0, r27 = 0, r28 = 0, r29 = 0;
  var r30 = 0, r31 = 0, r41 = 0, r42 = 0, r43 = 0, r44 = 0;
    r14 = env|0;
    r15 = sp_value|0;
  START: do {
    r0 = HEAPU32[((r14 + (-4))|0) >> 2] | 0;
    r42 = 0;
    result = ((r0|0) != (r42|0))|0;
    HEAPU32[1445307] = r0;
    HEAPU32[1445321] = r14;
    if(result|0) {
    HEAPU32[1445322] = r15;
    return 0x0345bf93|0;
    }
    r0 = HEAPU32[((r14 + (16))|0) >> 2] | 0;
    r42 = 8;
    r0 = ((r0|0) - (r42|0))|0;
    HEAPU32[(r14 + (16)) >> 2] = r0;
    r1 = 8;
    HEAPU32[(r14 + (44)) >> 2] = r1;
    r1 = r0|0;
    HEAPU32[(r14 + (40)) >> 2] = r1;
    r42 = 4;
    r0 = ((r0|0) + (r42|0))|0;
    r2 = HEAPU32[((r14 + (24))|0) >> 2] | 0;
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    HEAPU32[1445309] = r2;
    HEAPU32[1445321] = r14;
    HEAPU32[1445322] = r15;
    qemu_st_lel(env|0, r0|0, r2|0, 34, 22759218);
if(getThrew() | 0) abort();
    r0 = 3241038392;
    HEAPU32[1445307] = r0;
    r0 = qemu_ld_leul(env|0, r0|0, 34, 22759233)|0;
if(getThrew() | 0) abort();
    HEAPU32[(r14 + (24)) >> 2] = r0;
    r1 = HEAPU32[((r14 + (12))|0) >> 2] | 0;
    r2 = HEAPU32[((r14 + (40))|0) >> 2] | 0;
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    HEAPU32[1445309] = r2;
    qemu_st_lel(env|0, r2|0, r1|0, 34, 22759265);
if(getThrew() | 0) abort();
    r0 = HEAPU32[((r14 + (24))|0) >> 2] | 0;
    HEAPU32[(r14 + (40)) >> 2] = r0;
    r1 = 24;
    HEAPU32[(r14 + (52)) >> 2] = r1;
    r42 = 0;
    result = ((r0|0) == (r42|0))|0;
    if(result|0) {
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    }
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    return execute_if_compiled(22759392|0, env|0, sp_value|0, depth|0) | 0;
    return execute_if_compiled(23164080|0, env|0, sp_value|0, depth|0) | 0;
    break;
  } while(1); abort(); return 0|0;
}
return {tb_fun: tb_fun};
}(window, CompilerFFI, Module.buffer)["tb_fun"]

Konklusioun

Also, d'Aarbecht ass nach ëmmer net ofgeschloss, awer ech sinn midd fir dës laangfristeg Konstruktioun geheim zu Perfektioun ze bréngen. Dofir hunn ech decidéiert wat ech elo hunn ze publizéieren. De Code ass op Plazen e bëssen grujeleg, well dëst en Experiment ass, an et ass am Viraus net kloer wat gemaach muss ginn. Wahrscheinlech, dann ass et derwäert normal atomar Verpflichtungen uewen op enger méi moderner Versioun vu Qemu auszeginn. An der Zwëschenzäit gëtt et e Fuedem an der Gita an engem Blogformat: fir all "Niveau" deen op d'mannst iergendwéi passéiert ass, ass en detailléierte Kommentar op Russesch dobäikomm. Eigentlech ass dësen Artikel zu engem groussen Deel e Réckzuch vun der Conclusioun git log.

Dir kënnt alles probéieren hei (opgepasst vum Verkéier).

Wat scho funktionnéiert:

  • x86 virtuelle Prozessor lafen
  • Et gëtt e funktionnéierende Prototyp vun engem JIT Code Generator vu Maschinncode bis JavaScript
  • Et gëtt eng Schabloun fir aner 32-Bit Gaaschtarchitekturen ze montéieren: elo kënnt Dir Linux bewonneren fir d'MIPS Architektur déi am Browser an der Luedestadium afréiert

Wat soss kënnt Dir maachen

  • Beschleunegt d'Emuléierung. Och am JIT Modus schéngt et méi lues ze lafen wéi Virtual x86 (awer et gëtt potenziell e ganze Qemu mat vill emuléierter Hardware an Architekturen)
  • Fir eng normal Interface ze maachen - éierlech gesot, ech sinn net e gudde Webentwéckler, also fir elo hunn ech d'Standard Emscripten Shell sou gutt wéi méiglech nei gemaach
  • Probéiert méi komplex Qemu Funktiounen ze lancéieren - Netzwierker, VM Migratioun, etc.
  • UPS: Dir musst Är puer Entwécklungen a Käferberichter un Emscripten upstream ofginn, sou wéi fréier Porter vu Qemu an aner Projete gemaach hunn. Merci hinnen datt se hir Bäitrag zu Emscripten implizit als Deel vu menger Aufgab benotze kënnen.

Source: will.com

Setzt e Commentaire