Qemu.js με υποστήριξη JIT: μπορείτε ακόμα να γυρίσετε τον κιμά προς τα πίσω

Πριν από λίγα χρόνια ο Fabrice Bellard γραμμένο από jslinux είναι ένας εξομοιωτής υπολογιστή γραμμένος σε JavaScript. Μετά από αυτό, υπήρχαν τουλάχιστον περισσότερα Εικονική x86. Αλλά όλοι τους, απ' όσο ξέρω, ήταν διερμηνείς, ενώ το Qemu, που γράφτηκε πολύ νωρίτερα από τον ίδιο Fabrice Bellard, και, πιθανώς, οποιοσδήποτε σύγχρονος εξομοιωτής που σέβεται τον εαυτό του, χρησιμοποιεί τη συλλογή του κώδικα επισκέπτη JIT σε κώδικα συστήματος κεντρικού υπολογιστή. Μου φάνηκε ότι είχε έρθει η ώρα να εφαρμόσω την αντίθετη εργασία σε σχέση με αυτήν που επιλύουν τα προγράμματα περιήγησης: τη μεταγλώττιση JIT κώδικα μηχανής σε JavaScript, για την οποία φαινόταν πιο λογικό να μεταφερθεί το Qemu. Φαίνεται γιατί το Qemu, υπάρχουν απλούστεροι και φιλικοί προς το χρήστη εξομοιωτές - το ίδιο VirtualBox, για παράδειγμα - είναι εγκατεστημένοι και λειτουργούν. Αλλά το Qemu έχει πολλά ενδιαφέροντα χαρακτηριστικά

  • ανοιχτή πηγή
  • ικανότητα εργασίας χωρίς πρόγραμμα οδήγησης πυρήνα
  • ικανότητα εργασίας σε λειτουργία διερμηνέα
  • υποστήριξη για μεγάλο αριθμό αρχιτεκτονικών κεντρικών και επισκεπτών

Όσον αφορά το τρίτο σημείο, μπορώ τώρα να εξηγήσω ότι στην πραγματικότητα, στη λειτουργία TCI, δεν ερμηνεύονται οι ίδιες οι οδηγίες του επισκέπτη του μηχανήματος, αλλά ο bytecode που λαμβάνεται από αυτές, αλλά αυτό δεν αλλάζει την ουσία - προκειμένου να δημιουργηθεί και να εκτελεστεί Qemu σε μια νέα αρχιτεκτονική, αν είστε τυχεροί, ένας μεταγλωττιστής C είναι αρκετός - η σύνταξη μιας δημιουργίας κώδικα μπορεί να αναβληθεί.

Και τώρα, μετά από δύο χρόνια χαλαρής επεξεργασίας με τον πηγαίο κώδικα Qemu στον ελεύθερο χρόνο μου, εμφανίστηκε ένα λειτουργικό πρωτότυπο, στο οποίο μπορείτε ήδη να εκτελέσετε, για παράδειγμα, το Kolibri OS.

Τι είναι το Emscripten

Στις μέρες μας έχουν εμφανιστεί πολλοί μεταγλωττιστές, το τελικό αποτέλεσμα των οποίων είναι η JavaScript. Ορισμένα, όπως το Type Script, αρχικά προορίζονταν να είναι ο καλύτερος τρόπος γραφής για τον Ιστό. Ταυτόχρονα, το Emscripten είναι ένας τρόπος για να πάρετε τον υπάρχοντα κώδικα C ή C++ και να τον μεταγλωττίσετε σε μια αναγνώσιμη φόρμα από το πρόγραμμα περιήγησης. Επί αυτή τη σελίδα Έχουμε συλλέξει πολλές θύρες γνωστών προγραμμάτων: εδώΓια παράδειγμα, μπορείτε να δείτε το PyPy - παρεμπιπτόντως, ισχυρίζονται ότι έχουν ήδη JIT. Στην πραγματικότητα, δεν μπορεί απλά να μεταγλωττιστεί και να εκτελεστεί κάθε πρόγραμμα σε ένα πρόγραμμα περιήγησης - υπάρχει ένας αριθμός χαρακτηριστικά, το οποίο πρέπει να ανεχθείτε, ωστόσο, καθώς η επιγραφή στην ίδια σελίδα λέει «Το Emscripten μπορεί να χρησιμοποιηθεί για τη μεταγλώττιση σχεδόν οποιουδήποτε φορητός Κώδικας C/C++ σε JavaScript". Δηλαδή, υπάρχει ένας αριθμός λειτουργιών που έχουν απροσδιόριστη συμπεριφορά σύμφωνα με το πρότυπο, αλλά συνήθως λειτουργούν σε x86 - για παράδειγμα, μη ευθυγραμμισμένη πρόσβαση σε μεταβλητές, η οποία γενικά απαγορεύεται σε ορισμένες αρχιτεκτονικές. Γενικά , Το Qemu είναι ένα πρόγραμμα πολλαπλών πλατφορμών και, ήθελα να πιστεύω, και δεν περιέχει ήδη πολλές απροσδιόριστες συμπεριφορές - πάρτε το και κάντε μεταγλώττιση, μετά κάντε λίγο με το JIT - και τελειώσατε! Αλλά δεν είναι αυτό υπόθεση...

Πρώτη προσπάθεια

Σε γενικές γραμμές, δεν είμαι ο πρώτος που σκέφτηκε την ιδέα μεταφοράς του Qemu σε JavaScript. Έγινε μια ερώτηση στο φόρουμ του ReactOS εάν αυτό ήταν δυνατό χρησιμοποιώντας το Emscripten. Ακόμη και νωρίτερα, υπήρχαν φήμες ότι ο Fabrice Bellard το έκανε αυτό προσωπικά, αλλά μιλούσαμε για jslinux, το οποίο, από όσο ξέρω, είναι απλώς μια προσπάθεια χειροκίνητης επίτευξης επαρκών επιδόσεων στο JS και γράφτηκε από την αρχή. Αργότερα, γράφτηκε το Virtual x86 - αναρτήθηκαν χωρίς αμφιβολίες πηγές για αυτό και, όπως αναφέρθηκε, ο μεγαλύτερος "ρεαλισμός" της εξομοίωσης κατέστησε δυνατή τη χρήση του SeaBIOS ως υλικολογισμικού. Επιπλέον, υπήρξε τουλάχιστον μία προσπάθεια μεταφοράς του Qemu χρησιμοποιώντας το Emscripten - προσπάθησα να το κάνω αυτό πρίζα, αλλά η ανάπτυξη, από όσο καταλαβαίνω, ήταν παγωμένη.

Έτσι, φαίνεται, εδώ είναι οι πηγές, εδώ είναι το Emscripten - πάρτε το και μεταγλωττίστε. Υπάρχουν όμως και βιβλιοθήκες από τις οποίες εξαρτάται ο Qemu και βιβλιοθήκες από τις οποίες εξαρτώνται αυτές οι βιβλιοθήκες κ.λπ., και μία από αυτές είναι libffi, από το οποίο εξαρτάται η ολίσθηση. Υπήρχαν φήμες στο Διαδίκτυο ότι υπήρχε ένα στη μεγάλη συλλογή λιμένων βιβλιοθηκών για το Emscripten, αλλά ήταν κάπως δύσκολο να το πιστέψει κανείς: πρώτον, δεν προοριζόταν να γίνει νέος μεταγλωττιστής, δεύτερον, ήταν πολύ χαμηλού επιπέδου βιβλιοθήκη για να παραλάβετε και να μεταγλωττίσετε στο JS. Και δεν πρόκειται μόνο για ένθετα συναρμολόγησης - πιθανώς, αν το στρίψετε, για ορισμένες συμβάσεις κλήσης μπορείτε να δημιουργήσετε τα απαραίτητα ορίσματα στη στοίβα και να καλέσετε τη συνάρτηση χωρίς αυτά. Αλλά το Emscripten είναι ένα δύσκολο πράγμα: για να γίνει ο παραγόμενος κώδικας οικείος στο πρόγραμμα βελτιστοποίησης μηχανών JS του προγράμματος περιήγησης, χρησιμοποιούνται ορισμένα κόλπα. Συγκεκριμένα, το λεγόμενο relooping - μια γεννήτρια κώδικα που χρησιμοποιεί το λαμβανόμενο IR LLVM με κάποιες αφηρημένες οδηγίες μετάβασης προσπαθεί να αναδημιουργήσει εύλογα αν, βρόχους κ.λπ. Λοιπόν, πώς μεταβιβάζονται τα ορίσματα στη συνάρτηση; Φυσικά, ως ορίσματα στις συναρτήσεις JS, δηλαδή, αν είναι δυνατόν, όχι μέσω της στοίβας.

Στην αρχή υπήρχε μια ιδέα να γράψω απλώς μια αντικατάσταση για το libffi με JS και να εκτελέσω τυπικές δοκιμές, αλλά στο τέλος μπερδεύτηκα σχετικά με το πώς να φτιάξω τα αρχεία κεφαλίδας μου έτσι ώστε να λειτουργούν με τον υπάρχοντα κώδικα - τι μπορώ να κάνω; όπως λένε, "Είναι οι εργασίες τόσο περίπλοκες "Είμαστε τόσο ανόητοι;" Έπρεπε να μεταφέρω το libffi σε άλλη αρχιτεκτονική, να το πω έτσι - ευτυχώς, το Emscripten έχει και τις δύο μακροεντολές για ενσωματωμένη συναρμολόγηση (σε Javascript, ναι - όποια κι αν είναι η αρχιτεκτονική, άρα ο assembler) και τη δυνατότητα εκτέλεσης κώδικα που δημιουργείται εν κινήσει. Γενικά, αφού ασχολήθηκα με κομμάτια libffi που εξαρτώνται από πλατφόρμα για κάποιο χρονικό διάστημα, έλαβα κάποιο μεταγλωττιζόμενο κώδικα και τον έτρεξα στην πρώτη δοκιμή που συνάντησα. Προς έκπληξή μου, η δοκιμή ήταν επιτυχής. Ζαλισμένος από την ιδιοφυΐα μου -χωρίς πλάκα, λειτούργησε από την πρώτη εκτόξευση-, χωρίς να πιστεύω στα μάτια μου, πήγα να ξανακοιτάξω τον κώδικα που προέκυψε, για να αξιολογήσω πού να σκάψω στη συνέχεια. Εδώ τρελάθηκα για δεύτερη φορά - το μόνο πράγμα που έκανα η λειτουργία μου ήταν ffi_call - αυτό ανέφερε μια επιτυχημένη κλήση. Δεν υπήρχε η ίδια κλήση. Έτσι έστειλα το πρώτο μου αίτημα έλξης, το οποίο διόρθωσε ένα σφάλμα στο τεστ που είναι σαφές σε οποιονδήποτε μαθητή της Ολυμπιάδας - οι πραγματικοί αριθμοί δεν πρέπει να συγκρίνονται με a == b και μάλιστα πώς a - b < EPS - πρέπει επίσης να θυμάστε τη μονάδα, διαφορετικά το 0 θα αποδειχθεί πολύ ίσο με το 1/3... Γενικά, βρήκα μια συγκεκριμένη θύρα libffi, η οποία περνάει τις πιο απλές δοκιμές και με την οποία το glib είναι μεταγλωττισμένο - αποφάσισα ότι θα ήταν απαραίτητο, θα το προσθέσω αργότερα. Κοιτάζοντας μπροστά, θα πω ότι, όπως αποδείχθηκε, ο μεταγλωττιστής δεν συμπεριέλαβε καν τη συνάρτηση libffi στον τελικό κώδικα.

Αλλά, όπως είπα ήδη, υπάρχουν ορισμένοι περιορισμοί και μεταξύ της δωρεάν χρήσης διαφόρων απροσδιόριστων συμπεριφορών, έχει κρυφτεί ένα πιο δυσάρεστο χαρακτηριστικό - το JavaScript από τη σχεδίαση δεν υποστηρίζει πολλαπλές νήματα με κοινόχρηστη μνήμη. Κατ 'αρχήν, αυτό μπορεί συνήθως να ονομαστεί μια καλή ιδέα, αλλά όχι για τη μεταφορά κώδικα του οποίου η αρχιτεκτονική είναι συνδεδεμένη με νήματα C. Σε γενικές γραμμές, ο Firefox πειραματίζεται με την υποστήριξη κοινόχρηστων εργαζομένων και το Emscripten έχει μια εφαρμογή pthread για αυτούς, αλλά δεν ήθελα να βασιστώ σε αυτό. Έπρεπε σιγά-σιγά να ξεριζώσω το multithreading από τον κώδικα Qemu - δηλαδή, να μάθω πού τρέχουν τα νήματα, να μετακινήσω το σώμα του βρόχου που εκτελείται σε αυτό το νήμα σε μια ξεχωριστή συνάρτηση και να καλέσω αυτές τις συναρτήσεις μία προς μία από τον κύριο βρόχο.

Δεύτερη απόπειρα

Κάποια στιγμή, έγινε σαφές ότι το πρόβλημα εξακολουθούσε να υπάρχει και ότι το να σπρώχνοντας τυχαία πατερίτσες γύρω από τον κώδικα δεν θα οδηγούσε σε κανένα καλό. Συμπέρασμα: πρέπει να συστηματοποιήσουμε κάπως τη διαδικασία προσθήκης πατερίτσες. Επομένως, η έκδοση 2.4.1, που ήταν φρέσκια εκείνη την εποχή, λήφθηκε (όχι η 2.5.0, γιατί, ποιος ξέρει, θα υπάρχουν σφάλματα στη νέα έκδοση που δεν έχουν εντοπιστεί ακόμα και έχω αρκετά δικά μου σφάλματα ), και το πρώτο πράγμα ήταν να το ξαναγράψω με ασφάλεια thread-posix.c. Λοιπόν, δηλαδή, τόσο ασφαλές: εάν κάποιος προσπαθούσε να εκτελέσει μια λειτουργία που οδηγεί σε αποκλεισμό, η λειτουργία καλούνταν αμέσως abort() - φυσικά, αυτό δεν έλυσε όλα τα προβλήματα ταυτόχρονα, αλλά τουλάχιστον ήταν κάπως πιο ευχάριστο από την αθόρυβη λήψη ασυνεπών δεδομένων.

Γενικά, οι επιλογές Emscripten είναι πολύ χρήσιμες για τη μεταφορά κώδικα σε JS -s ASSERTIONS=1 -s SAFE_HEAP=1 - πιάνουν ορισμένους τύπους απροσδιόριστης συμπεριφοράς, όπως κλήσεις σε μη ευθυγραμμισμένη διεύθυνση (που δεν είναι καθόλου συνεπής με τον κώδικα για πληκτρολογημένους πίνακες όπως HEAP32[addr >> 2] = 1) ή καλώντας μια συνάρτηση με λάθος αριθμό ορισμάτων.

Παρεμπιπτόντως, τα σφάλματα ευθυγράμμισης είναι ένα ξεχωριστό ζήτημα. Όπως είπα ήδη, το Qemu έχει ένα «εκφυλισμένο» ερμηνευτικό backend για την παραγωγή κώδικα TCI (μικροσκοπικός διερμηνέας κώδικα) και για να δημιουργήσετε και να εκτελέσετε το Qemu σε μια νέα αρχιτεκτονική, αν είστε τυχεροί, αρκεί ένας μεταγλωττιστής C. Λέξεις-κλειδιά "αν είσαι τυχερός". Ήμουν άτυχος και αποδείχθηκε ότι το TCI χρησιμοποιεί μη ευθυγραμμισμένη πρόσβαση κατά την ανάλυση του bytecode του. Δηλαδή, σε όλα τα είδη ARM και άλλες αρχιτεκτονικές με κατ 'ανάγκη ισοπεδωμένη πρόσβαση, το Qemu μεταγλωττίζει επειδή έχουν ένα κανονικό backend TCG που δημιουργεί εγγενή κώδικα, αλλά αν το TCI θα λειτουργήσει σε αυτά είναι ένα άλλο ερώτημα. Ωστόσο, όπως αποδείχθηκε, η τεκμηρίωση της TCI ανέφερε ξεκάθαρα κάτι παρόμοιο. Ως αποτέλεσμα, προστέθηκαν στον κώδικα κλήσεις συναρτήσεων για μη ευθυγραμμισμένη ανάγνωση, οι οποίες ανακαλύφθηκαν σε άλλο μέρος του Qemu.

Καταστροφή σωρών

Ως αποτέλεσμα, διορθώθηκε η μη ευθυγραμμισμένη πρόσβαση στο TCI, δημιουργήθηκε ένας κύριος βρόχος που με τη σειρά του ονομαζόταν επεξεργαστής, RCU και μερικά άλλα μικρά πράγματα. Και έτσι εκκινώ το Qemu με την επιλογή -d exec,in_asm,out_asm, πράγμα που σημαίνει ότι πρέπει να πείτε ποια μπλοκ κώδικα εκτελούνται και επίσης τη στιγμή της μετάδοσης να γράψετε ποιος ήταν ο κωδικός επισκέπτη, ποιος έγινε ο κώδικας κεντρικού υπολογιστή (σε αυτήν την περίπτωση, bytecode). Ξεκινά, εκτελεί πολλά μπλοκ μετάφρασης, γράφει το μήνυμα αποσφαλμάτωσης που άφησα ότι τώρα θα ξεκινήσει το RCU και... κολλάει abort() μέσα σε μια συνάρτηση free(). Με το να μπερδεύουμε τη λειτουργία free() Καταφέραμε να μάθουμε ότι στην κεφαλίδα του μπλοκ σωρού, που βρίσκεται στα οκτώ byte που προηγούνται της εκχωρημένης μνήμης, αντί για το μέγεθος του μπλοκ ή κάτι παρόμοιο, υπήρχαν σκουπίδια.

Καταστροφή του σωρού - τι χαριτωμένο... Σε μια τέτοια περίπτωση, υπάρχει μια χρήσιμη θεραπεία - από (αν είναι δυνατόν) τις ίδιες πηγές, συναρμολογήστε ένα εγγενές δυαδικό και εκτελέστε το υπό Valgrind. Μετά από λίγο, το δυαδικό ήταν έτοιμο. Το εκκινώ με τις ίδιες επιλογές - κολλάει ακόμα και κατά την προετοιμασία, πριν φτάσει πραγματικά στην εκτέλεση. Είναι δυσάρεστο, φυσικά - προφανώς, οι πηγές δεν ήταν ακριβώς οι ίδιες, κάτι που δεν προκαλεί έκπληξη, επειδή η ρύθμιση παραμέτρων εντόπισε ελαφρώς διαφορετικές επιλογές, αλλά έχω το Valgrind - πρώτα θα διορθώσω αυτό το σφάλμα και μετά, αν είμαι τυχερός , θα εμφανιστεί το αρχικό. Εκτελώ το ίδιο πράγμα κάτω από το Valgrind... Νεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεεε,ξεκίνησε, πέρασε κανονικά από την προετοιμασία και πέρασε το αρχικό bug χωρίς ούτε μια προειδοποίηση για εσφαλμένη πρόσβαση στη μνήμη, για να μην αναφέρουμε για πτώσεις. Η ζωή, όπως λένε, δεν με προετοίμασε για αυτό - ένα πρόγραμμα συντριβής σταματά να συντρίβεται όταν εκτοξεύεται υπό τον Walgrind. Αυτό που ήταν είναι ένα μυστήριο. Η υπόθεσή μου είναι ότι μια φορά κοντά στην τρέχουσα εντολή μετά από μια συντριβή κατά την προετοιμασία, το gdb έδειξε εργασία memset-a με έγκυρο δείκτη χρησιμοποιώντας είτε mmx, ή xmm καταχωρεί, τότε ίσως ήταν κάποιο είδος σφάλματος ευθυγράμμισης, αν και είναι ακόμα δύσκολο να το πιστέψουμε.

Εντάξει, ο Valgrind δεν φαίνεται να βοηθά εδώ. Και εδώ ξεκίνησε το πιο αηδιαστικό πράγμα - όλα φαίνονται να ξεκινούν, αλλά συντρίβεται για απολύτως άγνωστους λόγους λόγω ενός γεγονότος που θα μπορούσε να είχε συμβεί πριν από εκατομμύρια οδηγίες. Για πολύ καιρό, δεν ήταν καν σαφές πώς να πλησιάσει. Στο τέλος, έπρεπε ακόμα να καθίσω και να διορθώσω σφάλματα. Εκτυπώνοντας αυτό με το οποίο γράφτηκε ξανά η κεφαλίδα έδειξε ότι δεν έμοιαζε με αριθμό, αλλά μάλλον με κάποιο είδος δυαδικών δεδομένων. Και, ιδού, αυτή η δυαδική συμβολοσειρά βρέθηκε στο αρχείο BIOS - δηλαδή, τώρα ήταν δυνατό να πούμε με εύλογη σιγουριά ότι ήταν υπερχείλιση buffer, και ήταν ακόμη σαφές ότι είχε γραφτεί σε αυτό το buffer. Λοιπόν, κάτι σαν αυτό - στο Emscripten, ευτυχώς, δεν υπάρχει τυχαιοποίηση του χώρου διευθύνσεων, δεν υπάρχουν ούτε τρύπες σε αυτόν, ώστε να μπορείτε να γράψετε κάπου στη μέση του κώδικα για να εξάγετε δεδομένα με δείκτη από την τελευταία εκκίνηση, κοιτάξτε τα δεδομένα, κοιτάξτε τον δείκτη και, αν δεν έχει αλλάξει, βρείτε τροφή για σκέψη. Είναι αλήθεια ότι χρειάζονται μερικά λεπτά για να συνδεθείτε μετά από οποιαδήποτε αλλαγή, αλλά τι μπορείτε να κάνετε; Ως αποτέλεσμα, βρέθηκε μια συγκεκριμένη γραμμή που αντέγραφε το BIOS από την προσωρινή προσωρινή μνήμη στη μνήμη επισκέπτη - και, πράγματι, δεν υπήρχε αρκετός χώρος στην προσωρινή μνήμη. Η εύρεση της πηγής αυτής της παράξενης διεύθυνσης buffer οδήγησε σε μια συνάρτηση qemu_anon_ram_alloc στο αρχείο oslib-posix.c - η λογική εκεί ήταν η εξής: μερικές φορές μπορεί να είναι χρήσιμο να ευθυγραμμίσετε τη διεύθυνση σε μια τεράστια σελίδα μεγέθους 2 MB, γι' αυτό θα ζητήσουμε mmap πρώτα λίγο ακόμα και μετά θα επιστρέψουμε το πλεόνασμα με τη βοήθεια munmap. Και αν δεν απαιτείται τέτοια ευθυγράμμιση, τότε θα υποδείξουμε το αποτέλεσμα αντί για 2 MB getpagesize() - mmap θα εξακολουθεί να δίνει μια ευθυγραμμισμένη διεύθυνση... Έτσι στο Emscripten mmap απλά καλεί malloc, αλλά φυσικά δεν ευθυγραμμίζεται στη σελίδα. Γενικά, ένα σφάλμα που με απογοήτευσε για μερικούς μήνες διορθώθηκε με μια αλλαγή δύοχ γραμμές.

Χαρακτηριστικά των συναρτήσεων κλήσης

Και τώρα ο επεξεργαστής μετράει κάτι, το Qemu δεν κολλάει, αλλά η οθόνη δεν ανάβει και ο επεξεργαστής πηγαίνει γρήγορα σε βρόχους, κρίνοντας από την έξοδο -d exec,in_asm,out_asm. Έχει προκύψει μια υπόθεση: οι διακοπές με χρονοδιακόπτη (ή, γενικά, όλες οι διακοπές) δεν φτάνουν. Και πράγματι, αν ξεβιδώσεις τις διακοπές από τη μητρική συναρμολόγηση, που για κάποιο λόγο λειτούργησε, έχεις παρόμοια εικόνα. Αλλά αυτή δεν ήταν καθόλου η απάντηση: μια σύγκριση των ιχνών που εκδόθηκαν με την παραπάνω επιλογή έδειξε ότι οι τροχιές εκτέλεσης διέφεραν πολύ νωρίς. Εδώ πρέπει να ειπωθεί ότι η σύγκριση αυτού που καταγράφηκε χρησιμοποιώντας τον εκτοξευτή emrun Η αποσφαλμάτωση της εξόδου με την έξοδο του εγγενούς συγκροτήματος δεν είναι μια εντελώς μηχανική διαδικασία. Δεν ξέρω ακριβώς πώς συνδέεται ένα πρόγραμμα που εκτελείται σε πρόγραμμα περιήγησης emrun, αλλά ορισμένες γραμμές στην έξοδο αποδεικνύεται ότι έχουν αναδιαταχθεί, επομένως η διαφορά στη διαφορά δεν είναι ακόμη λόγος να υποθέσουμε ότι οι τροχιές έχουν αποκλίνει. Σε γενικές γραμμές, έγινε σαφές ότι σύμφωνα με τις οδηγίες ljmpl υπάρχει μια μετάβαση σε διαφορετικές διευθύνσεις και ο bytecode που δημιουργείται είναι θεμελιωδώς διαφορετικός: ο ένας περιέχει μια εντολή για να καλέσει μια συνάρτηση βοηθού, ο άλλος όχι. Αφού γκουγκλάραμε τις οδηγίες και μελετήσαμε τον κώδικα που μεταφράζει αυτές τις οδηγίες, κατέστη σαφές ότι, πρώτον, αμέσως πριν από αυτό στο μητρώο cr0 Έγινε μια εγγραφή - χρησιμοποιώντας επίσης βοηθό - η οποία άλλαξε τον επεξεργαστή σε προστατευμένη λειτουργία και, δεύτερον, ότι η έκδοση js δεν άλλαξε ποτέ σε προστατευμένη λειτουργία. Αλλά το γεγονός είναι ότι ένα άλλο χαρακτηριστικό του Emscripten είναι η απροθυμία του να ανεχθεί κώδικα όπως η εφαρμογή οδηγιών call στο TCI, τον οποίο οποιοσδήποτε δείκτης συνάρτησης οδηγεί σε τύπο long long f(int arg0, .. int arg9) - Οι συναρτήσεις πρέπει να καλούνται με τον σωστό αριθμό ορισμάτων. Εάν παραβιαστεί αυτός ο κανόνας, ανάλογα με τις ρυθμίσεις εντοπισμού σφαλμάτων, το πρόγραμμα είτε θα διακοπεί (κάτι που είναι καλό) είτε θα καλέσει καθόλου τη λανθασμένη συνάρτηση (που θα είναι λυπηρό για τον εντοπισμό σφαλμάτων). Υπάρχει επίσης μια τρίτη επιλογή - ενεργοποιήστε τη δημιουργία περιτυλίξεων που προσθέτουν / αφαιρούν ορίσματα, αλλά συνολικά αυτά τα περιτυλίγματα καταλαμβάνουν πολύ χώρο, παρά το γεγονός ότι στην πραγματικότητα χρειάζομαι μόνο λίγο περισσότερα από εκατό περιτυλίγματα. Αυτό από μόνο του είναι πολύ λυπηρό, αλλά αποδείχθηκε ότι υπήρχε ένα πιο σοβαρό πρόβλημα: στον δημιουργημένο κώδικα των συναρτήσεων περιτυλίγματος, τα ορίσματα μετατράπηκαν και μετατράπηκαν, αλλά μερικές φορές η συνάρτηση με τα δημιουργούμενα ορίσματα δεν καλούνταν - καλά, όπως στο η εφαρμογή μου στο libffi. Δηλαδή κάποιοι βοηθοί απλά δεν εκτελέστηκαν.

Ευτυχώς, το Qemu έχει μηχανικά αναγνώσιμες λίστες βοηθών με τη μορφή αρχείου κεφαλίδας όπως

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

Χρησιμοποιούνται αρκετά αστεία: πρώτον, οι μακροεντολές επαναπροσδιορίζονται με τον πιο περίεργο τρόπο DEF_HELPER_n, και μετά ενεργοποιείται helper.h. Στο βαθμό που η μακροεντολή επεκτείνεται σε αρχικοποιητή δομής και κόμμα, και στη συνέχεια ορίζεται ένας πίνακας και αντί για στοιχεία - #include <helper.h> Ως αποτέλεσμα, είχα τελικά την ευκαιρία να δοκιμάσω τη βιβλιοθήκη στη δουλειά pyparsing, και γράφτηκε ένα σενάριο που δημιουργεί ακριβώς αυτά τα περιτυλίγματα για ακριβώς τις λειτουργίες για τις οποίες χρειάζονται.

Και έτσι, μετά από αυτό ο επεξεργαστής φάνηκε να λειτουργεί. Φαίνεται να οφείλεται στο ότι η οθόνη δεν προετοιμάστηκε ποτέ, αν και το memtest86+ μπόρεσε να εκτελεστεί στην εγγενή συγκρότηση. Εδώ είναι απαραίτητο να διευκρινιστεί ότι ο κωδικός εισόδου/εξόδου του μπλοκ Qemu είναι γραμμένος σε κορουτίνες. Το Emscripten έχει τη δική του πολύ δύσκολη εφαρμογή, αλλά έπρεπε να υποστηρίζεται στον κώδικα Qemu και μπορείτε να διορθώσετε τον επεξεργαστή τώρα: Το Qemu υποστηρίζει επιλογές -kernel, -initrd, -append, με το οποίο μπορείτε να εκκινήσετε το Linux ή, για παράδειγμα, το memtest86+, χωρίς να χρησιμοποιήσετε καθόλου συσκευές μπλοκ. Αλλά εδώ είναι το πρόβλημα: στην εγγενή συγκρότηση θα μπορούσε κανείς να δει την έξοδο του πυρήνα Linux στην κονσόλα με την επιλογή -nographic, και δεν υπάρχει έξοδος από το πρόγραμμα περιήγησης στο τερματικό από όπου ξεκίνησε emrun, δεν ήρθε. Δηλαδή, δεν είναι ξεκάθαρο: ο επεξεργαστής δεν λειτουργεί ή η έξοδος γραφικών δεν λειτουργεί. Και μετά μου πέρασε από το μυαλό να περιμένω λίγο. Αποδείχθηκε ότι "ο επεξεργαστής δεν κοιμάται, αλλά απλά αναβοσβήνει αργά" και μετά από περίπου πέντε λεπτά ο πυρήνας πέταξε ένα σωρό μηνύματα στην κονσόλα και συνέχισε να κρέμεται. Έγινε σαφές ότι ο επεξεργαστής, γενικά, λειτουργεί και πρέπει να ψάξουμε στον κώδικα για την εργασία με το SDL2. Δυστυχώς, δεν ξέρω πώς να χρησιμοποιήσω αυτήν τη βιβλιοθήκη, οπότε σε ορισμένα μέρη έπρεπε να ενεργήσω τυχαία. Κάποια στιγμή, η γραμμή parallel0 αναβοσβήνει στην οθόνη σε μπλε φόντο, κάτι που πρότεινε κάποιες σκέψεις. Στο τέλος, αποδείχθηκε ότι το πρόβλημα ήταν ότι το Qemu ανοίγει πολλά εικονικά παράθυρα σε ένα φυσικό παράθυρο, μεταξύ των οποίων μπορείτε να κάνετε εναλλαγή χρησιμοποιώντας Ctrl-Alt-n: λειτουργεί στην εγγενή έκδοση, αλλά όχι στο Emscripten. Αφού απαλλαγείτε από τα περιττά παράθυρα χρησιμοποιώντας επιλογές -monitor none -parallel none -serial none και οδηγίες για αναγκαστικά επανασχεδιασμό ολόκληρης της οθόνης σε κάθε καρέ, όλα λειτούργησαν ξαφνικά.

Κορουτίνες

Έτσι, η προσομοίωση στο πρόγραμμα περιήγησης λειτουργεί, αλλά δεν μπορείτε να εκτελέσετε τίποτα ενδιαφέρον μεμονωμένη δισκέτα σε αυτήν, επειδή δεν υπάρχει μπλοκ I/O - πρέπει να εφαρμόσετε υποστήριξη για κορουτίνες. Το Qemu έχει ήδη πολλά backends κορουτίνας, αλλά λόγω της φύσης της JavaScript και της γεννήτριας κώδικα Emscripten, δεν μπορείτε απλώς να αρχίσετε να κάνετε ταχυδακτυλουργικές στοίβες. Φαίνεται ότι "όλα έχουν φύγει, ο γύψος αφαιρείται", αλλά οι προγραμματιστές του Emscripten έχουν ήδη φροντίσει για τα πάντα. Αυτό υλοποιείται αρκετά αστείο: ας ονομάσουμε μια κλήση συνάρτησης σαν αυτή ύποπτη emscripten_sleep και αρκετές άλλες που χρησιμοποιούν τον μηχανισμό Asyncify, καθώς και κλήσεις δείκτη και κλήσεις σε οποιαδήποτε συνάρτηση όπου μία από τις δύο προηγούμενες περιπτώσεις μπορεί να εμφανιστεί πιο κάτω στη στοίβα. Και τώρα, πριν από κάθε ύποπτη κλήση, θα επιλέγουμε ένα ασύγχρονο περιβάλλον και αμέσως μετά την κλήση, θα ελέγχουμε αν έχει συμβεί μια ασύγχρονη κλήση και, εάν έχει, θα αποθηκεύσουμε όλες τις τοπικές μεταβλητές σε αυτό το ασύγχρονο περιβάλλον, υποδεικνύοντας ποια συνάρτηση για να μεταφέρουμε τον έλεγχο στο πότε πρέπει να συνεχίσουμε την εκτέλεση και να βγούμε από την τρέχουσα συνάρτηση. Εδώ υπάρχει περιθώριο μελέτης του αποτελέσματος άσωτος — για τις ανάγκες της συνέχισης της εκτέλεσης κώδικα μετά την επιστροφή από μια ασύγχρονη κλήση, ο μεταγλωττιστής δημιουργεί «απόμπες» της συνάρτησης, ξεκινώντας μετά από μια ύποπτη κλήση — όπως αυτό: εάν υπάρχουν n ύποπτες κλήσεις, τότε η συνάρτηση θα επεκταθεί κάπου n/ 2 φορές — αυτό είναι ακόμα, αν όχι Λάβετε υπόψη ότι μετά από κάθε δυνητικά ασύγχρονη κλήση, πρέπει να προσθέσετε αποθήκευση ορισμένων τοπικών μεταβλητών στην αρχική συνάρτηση. Στη συνέχεια, χρειάστηκε ακόμη και να γράψω ένα απλό σενάριο στην Python, το οποίο, με βάση ένα δεδομένο σύνολο από ιδιαίτερα υπερβολικά χρησιμοποιημένες συναρτήσεις που υποτίθεται ότι «δεν επιτρέπουν στον ασυγχρονισμό να περάσει μέσα τους» (δηλαδή, προώθηση στοίβας και όλα όσα μόλις περιέγραψα δεν εργασία σε αυτά), υποδεικνύει κλήσεις μέσω δεικτών στους οποίους οι συναρτήσεις θα πρέπει να αγνοηθούν από τον μεταγλωττιστή, έτσι ώστε αυτές οι συναρτήσεις να μην θεωρούνται ασύγχρονες. Και τότε τα αρχεία JS κάτω από 60 MB είναι σαφώς πάρα πολλά - ας πούμε τουλάχιστον 30. Αν και, κάποτε έφτιαχνα ένα σενάριο συναρμολόγησης και κατά λάθος πέταξα τις επιλογές σύνδεσης, μεταξύ των οποίων ήταν -O3. Εκτελώ τον κώδικα που δημιουργήθηκε και το Chromium κατατρώει τη μνήμη και κολλάει. Στη συνέχεια, κατά λάθος κοίταξα τι προσπαθούσε να κατεβάσει... Λοιπόν, τι να πω, θα είχα παγώσει κι εγώ αν μου ζητούσαν να μελετήσω και να βελτιστοποιήσω προσεκτικά ένα Javascript 500+ MB.

Δυστυχώς, οι έλεγχοι στον κώδικα βιβλιοθήκης υποστήριξης Asyncify δεν ήταν απόλυτα φιλικοί longjmp- που χρησιμοποιούνται στον εικονικό κώδικα επεξεργαστή, αλλά μετά από μια μικρή ενημέρωση κώδικα που απενεργοποιεί αυτούς τους ελέγχους και επαναφέρει με δύναμη τα περιβάλλοντα σαν να ήταν όλα καλά, ο κώδικας λειτούργησε. Και τότε άρχισε ένα περίεργο πράγμα: μερικές φορές ενεργοποιούνταν έλεγχοι στον κώδικα συγχρονισμού - οι ίδιοι που συντρίβουν τον κώδικα εάν, σύμφωνα με τη λογική εκτέλεσης, έπρεπε να αποκλειστεί - κάποιος προσπάθησε να αρπάξει ένα ήδη καταγεγραμμένο mutex. Ευτυχώς, αποδείχθηκε ότι αυτό δεν ήταν λογικό πρόβλημα στον σειριακό κώδικα - χρησιμοποιούσα απλώς την τυπική λειτουργικότητα του κύριου βρόχου που παρέχεται από το Emscripten, αλλά μερικές φορές η ασύγχρονη κλήση ξετύλιξε εντελώς τη στοίβα και εκείνη τη στιγμή θα αποτύγχανε setTimeout από τον κύριο βρόχο - έτσι, ο κώδικας εισήλθε στην επανάληψη του κύριου βρόχου χωρίς να φύγει από την προηγούμενη επανάληψη. Ξαναέγραψε σε έναν άπειρο βρόχο και emscripten_sleepκαι τα προβλήματα με τα mutexe σταμάτησαν. Ο κώδικας έχει γίνει ακόμη πιο λογικός - τελικά, στην πραγματικότητα, δεν έχω κώδικα που να προετοιμάζει το επόμενο καρέ κινούμενης εικόνας - ο επεξεργαστής απλώς υπολογίζει κάτι και η οθόνη ενημερώνεται περιοδικά. Ωστόσο, τα προβλήματα δεν σταμάτησαν εκεί: μερικές φορές η εκτέλεση του Qemu απλώς τελείωνε σιωπηλά χωρίς εξαιρέσεις ή σφάλματα. Εκείνη τη στιγμή το παράτησα, αλλά, κοιτάζοντας μπροστά, θα πω ότι το πρόβλημα ήταν το εξής: ο κώδικας κορουτίνας, στην πραγματικότητα, δεν χρησιμοποιεί setTimeout (ή τουλάχιστον όχι τόσο συχνά όσο νομίζετε): λειτουργία emscripten_yield απλώς ορίζει τη σημαία ασύγχρονης κλήσης. Το όλο θέμα είναι ότι emscripten_coroutine_next δεν είναι ασύγχρονη συνάρτηση: εσωτερικά ελέγχει τη σημαία, την επαναφέρει και μεταφέρει τον έλεγχο στο σημείο που χρειάζεται. Δηλαδή, η προώθηση του stack τελειώνει εκεί. Το πρόβλημα ήταν ότι λόγω χρήσης-μετά-δωρεάν, που εμφανίστηκε όταν απενεργοποιήθηκε η ομάδα κορουτίνας λόγω του γεγονότος ότι δεν αντιγράφω μια σημαντική γραμμή κώδικα από το υπάρχον backend κορουτίνας, η συνάρτηση qemu_in_coroutine επέστρεψε true ενώ στην πραγματικότητα θα έπρεπε να είχε επιστρέψει false. Αυτό οδήγησε σε κλήση emscripten_yield, πάνω από το οποίο δεν υπήρχε κανείς στη στοίβα emscripten_coroutine_next, η στοίβα ξεδιπλώθηκε μέχρι την κορυφή, αλλά όχι setTimeout, όπως είπα ήδη, δεν εκτέθηκε.

Δημιουργία κώδικα JavaScript

Και εδώ, στην πραγματικότητα, είναι το υποσχεμένο «γυρίζοντας τον κιμά πίσω». Όχι πραγματικά. Φυσικά, αν τρέξουμε το Qemu στο πρόγραμμα περιήγησης και το Node.js σε αυτό, τότε, φυσικά, μετά τη δημιουργία κώδικα στο Qemu θα έχουμε εντελώς λάθος JavaScript. Αλλά και πάλι, κάποιου είδους αντίστροφη μεταμόρφωση.

Πρώτον, λίγα λόγια για το πώς λειτουργεί το Qemu. Συγχωρέστε με αμέσως: δεν είμαι επαγγελματίας προγραμματιστής του Qemu και τα συμπεράσματά μου μπορεί να είναι λανθασμένα σε ορισμένα σημεία. Όπως λένε, «η γνώμη του μαθητή δεν χρειάζεται να συμπίπτει με τη γνώμη του δασκάλου, την αξιωματική και την κοινή λογική του Peano». Το Qemu έχει έναν ορισμένο αριθμό υποστηριζόμενων αρχιτεκτονικών επισκεπτών και για καθεμία υπάρχει ένας κατάλογος όπως target-i386. Κατά τη δημιουργία, μπορείτε να καθορίσετε υποστήριξη για πολλές αρχιτεκτονικές φιλοξενούμενων, αλλά το αποτέλεσμα θα είναι απλώς πολλά δυαδικά. Ο κώδικας για την υποστήριξη της αρχιτεκτονικής επισκέπτη, με τη σειρά του, δημιουργεί ορισμένες εσωτερικές λειτουργίες Qemu, τις οποίες το TCG (Tiny Code Generator) μετατρέπει ήδη σε κώδικα μηχανής για την αρχιτεκτονική του κεντρικού υπολογιστή. Όπως αναφέρεται στο αρχείο readme που βρίσκεται στον κατάλογο tcg, αυτό ήταν αρχικά μέρος ενός κανονικού μεταγλωττιστή C, ο οποίος αργότερα προσαρμόστηκε για JIT. Επομένως, για παράδειγμα, η αρχιτεκτονική στόχος όσον αφορά αυτό το έγγραφο δεν είναι πλέον αρχιτεκτονική επισκέπτης, αλλά αρχιτεκτονική κεντρικού υπολογιστή. Σε κάποιο σημείο, εμφανίστηκε ένα άλλο στοιχείο - το Tiny Code Interpreter (TCI), το οποίο θα πρέπει να εκτελεί κώδικα (σχεδόν τις ίδιες εσωτερικές λειτουργίες) ελλείψει μιας γεννήτριας κώδικα για μια συγκεκριμένη αρχιτεκτονική κεντρικού υπολογιστή. Πράγματι, όπως αναφέρει η τεκμηρίωσή του, αυτός ο διερμηνέας μπορεί να μην αποδίδει πάντα τόσο καλά όσο μια γεννήτρια κώδικα JIT, όχι μόνο ποσοτικά ως προς την ταχύτητα, αλλά και ποιοτικά. Αν και δεν είμαι σίγουρος ότι η περιγραφή του είναι απολύτως σχετική.

Στην αρχή προσπάθησα να φτιάξω ένα πλήρες TCG backend, αλλά γρήγορα μπερδεύτηκα με τον πηγαίο κώδικα και μια όχι εντελώς σαφή περιγραφή των οδηγιών του bytecode, έτσι αποφάσισα να τυλίξω τον διερμηνέα TCI. Αυτό έδωσε πολλά πλεονεκτήματα:

  • κατά την υλοποίηση μιας δημιουργίας κώδικα, θα μπορούσατε να δείτε όχι την περιγραφή των εντολών, αλλά τον κώδικα διερμηνέα
  • μπορείτε να δημιουργήσετε συναρτήσεις όχι για κάθε μπλοκ μετάφρασης που συναντάται, αλλά, για παράδειγμα, μόνο μετά την εκατοστή εκτέλεση
  • αν αλλάξει ο δημιουργημένος κώδικας (και αυτό φαίνεται να είναι δυνατό, κρίνοντας από τις συναρτήσεις με ονόματα που περιέχουν την ενημερωμένη έκδοση κώδικα λέξης), θα χρειαστεί να ακυρώσω τον κώδικα JS που δημιουργήθηκε, αλλά τουλάχιστον θα έχω κάτι για να τον αναδημιουργήσω

Όσον αφορά το τρίτο σημείο, δεν είμαι σίγουρος ότι η ενημέρωση κώδικα είναι δυνατή μετά την πρώτη εκτέλεση του κώδικα, αλλά τα δύο πρώτα σημεία είναι αρκετά.

Αρχικά, ο κώδικας δημιουργήθηκε με τη μορφή ενός μεγάλου διακόπτη στη διεύθυνση της αρχικής εντολής bytecode, αλλά στη συνέχεια, θυμίζοντας το άρθρο σχετικά με το Emscripten, τη βελτιστοποίηση των δημιουργούμενων JS και τον επαναληπτικό κύκλο, αποφάσισα να δημιουργήσω περισσότερο ανθρώπινο κώδικα, ειδικά επειδή εμπειρικά αυτό αποδείχθηκε ότι το μόνο σημείο εισόδου στο μπλοκ μετάφρασης είναι η Έναρξή του. Πριν από λίγο, μετά από λίγο είχαμε μια γεννήτρια κώδικα που δημιουργούσε κώδικα με ifs (αν και χωρίς βρόχους). Αλλά κακή τύχη, χάλασε, δίνοντας ένα μήνυμα ότι οι οδηγίες ήταν λανθασμένες. Επιπλέον, η τελευταία οδηγία σε αυτό το επίπεδο αναδρομής ήταν brcond. Εντάξει, θα προσθέσω έναν πανομοιότυπο έλεγχο στη δημιουργία αυτής της εντολής πριν και μετά την αναδρομική κλήση και... δεν εκτελέστηκε ούτε μία από αυτές, αλλά μετά τον διακόπτη διεκδίκησης εξακολουθούν να απέτυχαν. Στο τέλος, αφού μελέτησα τον παραγόμενο κώδικα, συνειδητοποίησα ότι μετά την αλλαγή, ο δείκτης προς την τρέχουσα εντολή φορτώνεται ξανά από τη στοίβα και πιθανώς αντικαθίσταται από τον κώδικα JavaScript που δημιουργείται. Και έτσι αποδείχτηκε. Η αύξηση του buffer από ένα megabyte σε δέκα δεν οδήγησε σε τίποτα και έγινε σαφές ότι η γεννήτρια κώδικα λειτουργούσε σε κύκλους. Έπρεπε να ελέγξουμε ότι δεν περάσαμε πέρα ​​από τα όρια της τρέχουσας φυματίωσης, και αν το κάναμε, τότε εκδώστε τη διεύθυνση της επόμενης φυματίωσης με το σύμβολο μείον, ώστε να μπορέσουμε να συνεχίσουμε την εκτέλεση. Επιπλέον, αυτό λύνει το πρόβλημα "ποιες συναρτήσεις που δημιουργούνται θα πρέπει να ακυρωθούν εάν αυτό το κομμάτι bytecode έχει αλλάξει;" — μόνο η συνάρτηση που αντιστοιχεί σε αυτό το μπλοκ μετάφρασης πρέπει να ακυρωθεί. Παρεμπιπτόντως, παρόλο που διόρθωσα τα πάντα στο Chromium (καθώς χρησιμοποιώ τον Firefox και είναι πιο εύκολο για μένα να χρησιμοποιώ ξεχωριστό πρόγραμμα περιήγησης για πειράματα), ο Firefox με βοήθησε να διορθώσω τις ασυμβατότητες με το πρότυπο asm.js, μετά από το οποίο ο κώδικας άρχισε να λειτουργεί πιο γρήγορα στο Χρώμιο.

Παράδειγμα παραγόμενου κώδικα

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"]

Συμπέρασμα

Έτσι, το έργο δεν έχει ακόμα ολοκληρωθεί, αλλά έχω βαρεθεί να φέρνω κρυφά αυτή τη μακροπρόθεσμη κατασκευή στην τελειότητα. Ως εκ τούτου, αποφάσισα να δημοσιεύσω αυτό που έχω προς το παρόν. Ο κώδικας είναι λίγο τρομακτικός κατά τόπους, γιατί αυτό είναι ένα πείραμα και δεν είναι ξεκάθαρο εκ των προτέρων τι πρέπει να γίνει. Πιθανώς, τότε αξίζει να εκδώσετε κανονικές ατομικές δεσμεύσεις πάνω από κάποια πιο σύγχρονη έκδοση του Qemu. Εν τω μεταξύ, υπάρχει ένα νήμα στο Gita σε μορφή ιστολογίου: για κάθε "επίπεδο" που έχει περάσει τουλάχιστον με κάποιο τρόπο, προστέθηκε ένα λεπτομερές σχόλιο στα ρωσικά. Στην πραγματικότητα, αυτό το άρθρο είναι σε μεγάλο βαθμό μια επανάληψη του συμπεράσματος git log.

Μπορείτε να τα δοκιμάσετε όλα εδώ (προσοχή στην κίνηση).

Τι λειτουργεί ήδη:

  • x86 εικονικός επεξεργαστής σε λειτουργία
  • Υπάρχει ένα λειτουργικό πρωτότυπο μιας γεννήτριας κώδικα JIT από κώδικα μηχανής σε JavaScript
  • Υπάρχει ένα πρότυπο για τη συναρμολόγηση άλλων αρχιτεκτονικών επισκεπτών 32-bit: αυτή τη στιγμή μπορείτε να θαυμάσετε το Linux για την παγίωση της αρχιτεκτονικής MIPS στο πρόγραμμα περιήγησης στο στάδιο φόρτωσης

Τι άλλο μπορείς να κάνεις

  • Επιταχύνετε την εξομοίωση. Ακόμη και στη λειτουργία JIT φαίνεται να τρέχει πιο αργά από το Virtual x86 (αλλά υπάρχει δυνητικά ένα ολόκληρο Qemu με πολύ προσομοιωμένο υλικό και αρχιτεκτονικές)
  • Για να δημιουργήσω μια κανονική διεπαφή - ειλικρινά, δεν είμαι καλός προγραμματιστής ιστού, οπότε προς το παρόν έχω ξαναφτιάξει το τυπικό κέλυφος Emscripten όσο καλύτερα μπορώ
  • Προσπαθήστε να εκκινήσετε πιο σύνθετες λειτουργίες Qemu - δικτύωση, μετεγκατάσταση VM κ.λπ.
  • UPD: θα χρειαστεί να υποβάλετε τις λίγες εξελίξεις και αναφορές σφαλμάτων στο Emscripten upstream, όπως έκαναν οι προηγούμενοι αχθοφόροι του Qemu και άλλων έργων. Τους ευχαριστώ που μπόρεσαν να χρησιμοποιήσουν σιωπηρά τη συνεισφορά τους στο Emscripten ως μέρος της αποστολής μου.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο