QEMU.js: இப்போது தீவிரமானது மற்றும் WASM உடன்

ஒரு காலத்தில் நான் வேடிக்கையாக முடிவு செய்தேன் செயல்முறையின் மீள்தன்மையை நிரூபிக்கவும் இயந்திரக் குறியீட்டிலிருந்து ஜாவாஸ்கிரிப்டை (இன்னும் துல்லியமாக, Asm.js) உருவாக்குவது எப்படி என்பதை அறியவும். சோதனைக்காக QEMU தேர்ந்தெடுக்கப்பட்டது, சிறிது நேரம் கழித்து ஹப்ரில் ஒரு கட்டுரை எழுதப்பட்டது. கருத்துகளில், திட்டத்தை WebAssembly இல் ரீமேக் செய்யும்படி எனக்கு அறிவுறுத்தப்பட்டது, மேலும் என்னை விட்டு விலகினேன் கிட்டத்தட்ட முடிந்துவிட்டது நான் எப்படியோ திட்டத்தை விரும்பவில்லை ... வேலை நடந்து கொண்டிருந்தது, ஆனால் மிக மெதுவாக, இப்போது, ​​சமீபத்தில் அந்த கட்டுரையில் தோன்றியது கருத்து "அப்படியானால் எல்லாம் எப்படி முடிந்தது?" என்ற தலைப்பில் எனது விரிவான பதிலுக்கு பதிலளிக்கும் விதமாக, "இது ஒரு கட்டுரை போல் தெரிகிறது" என்று கேட்டேன். சரி, உங்களால் முடிந்தால், ஒரு கட்டுரை இருக்கும். ஒருவேளை யாராவது அதை பயனுள்ளதாகக் காணலாம். அதிலிருந்து வாசகர்கள் QEMU குறியீடு உருவாக்கப் பின்தளங்களின் வடிவமைப்பு மற்றும் இணையப் பயன்பாட்டிற்கான ஜஸ்ட்-இன்-டைம் கம்பைலரை எழுதுவது பற்றிய சில உண்மைகளை அறிந்து கொள்வார்கள்.

பணிகளை

ஜாவாஸ்கிரிப்ட்டில் QEMU போர்ட் செய்வது எப்படி என்பதை நான் ஏற்கனவே கற்றுக்கொண்டதால், இந்த முறை அதை புத்திசாலித்தனமாக செய்ய முடிவு செய்யப்பட்டது மற்றும் பழைய தவறுகளை மீண்டும் செய்ய வேண்டாம்.

பிழை எண் ஒன்று: புள்ளி வெளியீட்டிலிருந்து கிளை

எனது முதல் தவறு அப்ஸ்ட்ரீம் பதிப்பு 2.4.1 இலிருந்து எனது பதிப்பை பிரித்தது. பின்னர் இது ஒரு நல்ல யோசனையாக எனக்குத் தோன்றியது: புள்ளி வெளியீடு இருந்தால், அது எளிய 2.4 ஐ விட நிலையானதாக இருக்கலாம், மேலும் கிளை master. எனது சொந்த பிழைகளை நியாயமான அளவில் சேர்க்க நான் திட்டமிட்டிருந்ததால், எனக்கு வேறு யாருடைய பிழைகளும் தேவையில்லை. ஒருவேளை அப்படித்தான் மாறியது. ஆனால் இங்கே விஷயம் இதுதான்: QEMU இன்னும் நிற்கவில்லை, மேலும் ஒரு கட்டத்தில் அவர்கள் உருவாக்கிய குறியீட்டை 10 சதவிகிதம் மேம்படுத்துவதாக அறிவித்தனர். "ஆம், இப்போது நான் உறைந்து போகிறேன்," என்று நான் நினைத்து உடைந்தேன். இங்கே நாம் ஒரு திசைதிருப்பல் செய்ய வேண்டும்: QEMU.js இன் ஒற்றை-திரிக்கப்பட்ட தன்மை மற்றும் அசல் QEMU மல்டி-த்ரெடிங் இல்லாததைக் குறிக்கவில்லை (அதாவது, பல தொடர்பற்ற குறியீடு பாதைகளை ஒரே நேரத்தில் இயக்கும் திறன், மற்றும் "அனைத்து கர்னல்களையும் பயன்படுத்து" என்பது மட்டும் அல்ல) இதற்கு முக்கியமானது, வெளியில் இருந்து அழைக்க நான் "அதை மாற்ற" வேண்டிய நூல்களின் முக்கிய செயல்பாடுகள். இது இணைப்பின் போது சில இயற்கைச் சிக்கல்களை உருவாக்கியது. இருப்பினும், கிளையில் இருந்து சில மாற்றங்கள் master, நான் எனது குறியீட்டை ஒன்றிணைக்க முயற்சித்தேன், புள்ளி வெளியீட்டில் செர்ரி தேர்ந்தெடுக்கப்பட்டது (எனது கிளையில்) மேலும் வசதியைச் சேர்த்திருக்க வாய்ப்பில்லை.

பொதுவாக, முன்மாதிரியை தூக்கி எறிந்து, பகுதிகளாக பிரித்து, புதிதாக ஒன்றை அடிப்படையாகக் கொண்டு புதிய பதிப்பை உருவாக்குவது இன்னும் அர்த்தமுள்ளதாக நான் முடிவு செய்தேன். master.

தவறு எண் இரண்டு: TLP முறை

சாராம்சத்தில், இது ஒரு தவறு அல்ல, பொதுவாக, இது "எங்கு, எப்படி நகர்த்துவது?" மற்றும் பொதுவாக "நாங்கள் அங்கு செல்வோமா?" ஆகிய இரண்டின் முழுமையான தவறான புரிதலின் நிலைமைகளில் ஒரு திட்டத்தை உருவாக்கும் ஒரு அம்சமாகும். இந்த நிலைமைகளில் விகாரமான நிரலாக்க ஒரு நியாயமான விருப்பமாக இருந்தது, ஆனால், இயற்கையாகவே, நான் தேவையில்லாமல் அதை மீண்டும் செய்ய விரும்பவில்லை. இந்த நேரத்தில் நான் அதை புத்திசாலித்தனமாக செய்ய விரும்பினேன்: அணு உறுதிப்பாடுகள், நனவான குறியீடு மாற்றங்கள் (மற்றும் "தொகுக்கும் வரை (எச்சரிக்கைகளுடன்) சீரற்ற எழுத்துக்களை ஒன்றாக இணைக்கக்கூடாது", விக்கிமேற்கோட்டின் படி லினஸ் டொர்வால்ட்ஸ் ஒருவரைப் பற்றி ஒருமுறை கூறியது போல) போன்றவை.

தவறு எண் மூன்று: கோட்டை தெரியாமல் தண்ணீரில் இறங்குவது

நான் இன்னும் இதிலிருந்து முற்றிலும் விடுபடவில்லை, ஆனால் இப்போது நான் குறைந்தபட்ச எதிர்ப்பின் பாதையைப் பின்பற்ற வேண்டாம் என்று முடிவு செய்துள்ளேன், மேலும் அதை “வயதானவராக” செய்ய முடிவு செய்துள்ளேன், அதாவது, எனது TCG பின்தளத்தை புதிதாக எழுதுங்கள். பின்னர் சொல்ல வேண்டும், "ஆம், இது நிச்சயமாக, மெதுவாக, ஆனால் என்னால் எல்லாவற்றையும் கட்டுப்படுத்த முடியாது - TCI இப்படித்தான் எழுதப்பட்டுள்ளது..." மேலும், இது ஆரம்பத்தில் ஒரு தெளிவான தீர்வாகத் தோன்றியது நான் பைனரி குறியீட்டை உருவாக்குகிறேன். அவர்கள் சொல்வது போல், “கென்ட் கூடினார்у, ஆனால் அது ஒன்று அல்ல”: குறியீடு, நிச்சயமாக, பைனரி, ஆனால் கட்டுப்பாட்டை அதற்கு மாற்ற முடியாது - இது வெளிப்படையாக உலாவியில் தொகுக்கப்பட வேண்டும், இதன் விளைவாக JS உலகத்திலிருந்து ஒரு குறிப்பிட்ட பொருள் இன்னும் தேவைப்படுகிறது. எங்காவது சேமிக்கப்படும். இருப்பினும், சாதாரண RISC கட்டமைப்புகளில், நான் புரிந்துகொண்டவரை, ஒரு பொதுவான சூழ்நிலையானது, மறுஉருவாக்கம் செய்யப்பட்ட குறியீட்டிற்கான அறிவுறுத்தல் தற்காலிக சேமிப்பை வெளிப்படையாக மீட்டமைக்க வேண்டிய அவசியம் - இது நமக்குத் தேவைப்படாவிட்டால், எப்படியிருந்தாலும், அது நெருக்கமாக உள்ளது. கூடுதலாக, எனது கடைசி முயற்சியில் இருந்து, கட்டுப்பாடு மொழிபெயர்ப்புத் தொகுதியின் நடுப்பகுதிக்கு மாற்றப்பட்டதாகத் தெரியவில்லை, எனவே எந்த ஆஃப்செட்டிலிருந்தும் பைட்கோட் விளக்கப்பட வேண்டிய அவசியமில்லை, மேலும் TB இல் உள்ள செயல்பாட்டிலிருந்து அதை உருவாக்கலாம். .

வந்து அடித்தார்கள்

ஜூலையில் நான் குறியீட்டை மீண்டும் எழுதத் தொடங்கினாலும், ஒரு மேஜிக் கிக் கவனிக்கப்படாமல் வந்தது: வழக்கமாக GitHub இலிருந்து வரும் கடிதங்கள் சிக்கல்கள் மற்றும் இழுப்பு கோரிக்கைகளுக்கான பதில்கள் பற்றிய அறிவிப்புகளாக வரும், ஆனால் இங்கே, திடீரென்று நூலில் குறிப்பிடப்பட்டுள்ளது பைனரேன் ஒரு கேமு பின்தளமாக சூழலில், "அவர் அப்படி ஏதாவது செய்தார், ஒருவேளை அவர் ஏதாவது சொல்லலாம்." எம்ஸ்கிரிப்டன் தொடர்பான நூலகத்தைப் பயன்படுத்துவதைப் பற்றி பேசிக்கொண்டிருந்தோம் பைனரேன் WASM JIT ஐ உருவாக்க. சரி, உங்களிடம் Apache 2.0 உரிமம் உள்ளது என்றும், QEMU ஒட்டுமொத்தமாக GPLv2 இன் கீழ் விநியோகிக்கப்படுகிறது என்றும், அவை மிகவும் இணக்கமாக இல்லை என்றும் நான் கூறினேன். திடீரென்று உரிமம் இருக்கலாம் என்று மாறியது எப்படியாவது சரி செய் (எனக்குத் தெரியாது: ஒருவேளை அதை மாற்றலாம், ஒருவேளை இரட்டை உரிமம், ஒருவேளை வேறு ஏதாவது...). இது நிச்சயமாக எனக்கு மகிழ்ச்சியைத் தந்தது, ஏனென்றால் அந்த நேரத்தில் நான் ஏற்கனவே உன்னிப்பாகப் பார்த்தேன் பைனரி வடிவம் WebAssembly, மற்றும் நான் எப்படியோ சோகமாகவும் புரிந்துகொள்ள முடியாததாகவும் இருந்தேன். ட்ரான்ஸிஷன் கிராஃப் மூலம் அடிப்படைத் தொகுதிகளை விழுங்கி, பைட்கோடை உருவாக்கி, தேவைப்பட்டால், மொழிபெயர்ப்பாளிலேயே அதை இயக்கும் ஒரு நூலகமும் இருந்தது.

பின்னர் மேலும் இருந்தது ஒரு கடிதம் QEMU அஞ்சல் பட்டியலில் உள்ளது, ஆனால் இது "எப்படியும் யாருக்கு தேவை?" என்ற கேள்வியைப் பற்றியது. மற்றும் அது திடீரென்று, அது அவசியம் என்று மாறியது. குறைந்தபட்சம், இது அதிகமாகவோ அல்லது குறைவாகவோ விரைவாகச் செயல்பட்டால், பின்வரும் பயன்பாட்டின் சாத்தியக்கூறுகளை நீங்கள் ஒன்றாகச் சேர்க்கலாம்:

  • எந்த நிறுவலும் இல்லாமல் ஏதாவது கல்வியைத் தொடங்குதல்
  • iOS இல் மெய்நிகராக்கம், வதந்திகளின்படி, பறக்கும்போது குறியீட்டை உருவாக்குவதற்கான உரிமையைக் கொண்ட ஒரே பயன்பாடு JS இயந்திரம் (இது உண்மையா?)
  • மினி-ஓஎஸ்-ன் ஆர்ப்பாட்டம் - ஒற்றை நெகிழ், உள்ளமைக்கப்பட்ட, அனைத்து வகையான ஃபார்ம்வேர், முதலியன...

உலாவி இயக்க நேர அம்சங்கள்

நான் ஏற்கனவே கூறியது போல், QEMU மல்டித்ரெடிங்குடன் இணைக்கப்பட்டுள்ளது, ஆனால் உலாவியில் அது இல்லை. சரி, அதாவது, இல்லை... முதலில் அது இல்லை, பின்னர் WebWorkers தோன்றியது - நான் புரிந்து கொண்டவரை, இது செய்தி அனுப்புதலின் அடிப்படையில் மல்டித்ரெடிங் ஆகும் பகிரப்பட்ட மாறிகள் இல்லாமல். இயற்கையாகவே, பகிரப்பட்ட நினைவக மாதிரியின் அடிப்படையில் இருக்கும் குறியீட்டை போர்ட் செய்யும் போது இது குறிப்பிடத்தக்க சிக்கல்களை உருவாக்குகிறது. பின்னர், மக்களின் அழுத்தத்தின் பேரில் அதுவும் செயல்படுத்தப்பட்டது SharedArrayBuffers. இது படிப்படியாக அறிமுகப்படுத்தப்பட்டது, வெவ்வேறு உலாவிகளில் அதன் வெளியீட்டைக் கொண்டாடினர், பின்னர் அவர்கள் புத்தாண்டைக் கொண்டாடினர், பின்னர் மெல்ட் டவுன்... அதன் பிறகு அவர்கள் நேர அளவீடு கரடுமுரடான அல்லது கரடுமுரடானதாக முடிவுக்கு வந்தனர், ஆனால் பகிரப்பட்ட நினைவகத்தின் உதவியுடன் கவுண்டரை அதிகரிக்கும் நூல், எல்லாம் ஒன்றுதான் அது மிகவும் துல்லியமாக வேலை செய்யும். எனவே பகிர்ந்த நினைவகத்துடன் மல்டித்ரெடிங்கை முடக்கினோம். அவர்கள் பின்னர் அதை மீண்டும் இயக்கியதாகத் தெரிகிறது, ஆனால், முதல் பரிசோதனையிலிருந்து தெளிவாகத் தெரிந்ததால், அது இல்லாமல் வாழ்க்கை இருக்கிறது, அப்படியானால், மல்டித்ரெடிங்கை நம்பாமல் அதைச் செய்ய முயற்சிப்போம்.

இரண்டாவது அம்சம், ஸ்டேக்குடன் குறைந்த-நிலை கையாளுதல்களின் சாத்தியமற்றது: நீங்கள் வெறுமனே எடுக்க முடியாது, தற்போதைய சூழலைச் சேமிக்கவும் மற்றும் புதிய அடுக்குடன் புதியதாக மாறவும் முடியாது. அழைப்பு அடுக்கு JS மெய்நிகர் இயந்திரத்தால் நிர்வகிக்கப்படுகிறது. முந்தைய ஓட்டங்களை முழுமையாக கைமுறையாக நிர்வகிக்க நாங்கள் இன்னும் முடிவு செய்ததால், என்ன பிரச்சனை என்று தோன்றுகிறது? உண்மை என்னவென்றால், QEMU இல் உள்ள தொகுதி I/O ஆனது coroutines மூலம் செயல்படுத்தப்படுகிறது, மேலும் இங்குதான் குறைந்த அளவிலான ஸ்டாக் கையாளுதல்கள் பயனுள்ளதாக இருக்கும். அதிர்ஷ்டவசமாக, Emscipten ஏற்கனவே ஒத்திசைவற்ற செயல்பாடுகளுக்கான பொறிமுறையைக் கொண்டுள்ளது, இரண்டு: ஒத்திசைவு и பொறியாளர். முதலில் உருவாக்கப்பட்ட ஜாவாஸ்கிரிப்ட் குறியீட்டில் குறிப்பிடத்தக்க ப்ளோட் மூலம் செயல்படும் மற்றும் இனி ஆதரிக்கப்படாது. இரண்டாவது தற்போதைய "சரியான வழி" மற்றும் சொந்த மொழிபெயர்ப்பாளருக்கான பைட்கோட் உருவாக்கம் மூலம் செயல்படுகிறது. இது மெதுவாக வேலை செய்கிறது, ஆனால் அது குறியீட்டை வீங்குவதில்லை. உண்மை, இந்த பொறிமுறைக்கான கொரூட்டின்களுக்கான ஆதரவு சுயாதீனமாக வழங்கப்பட வேண்டும் (ஏற்கனவே Asyncify க்காக எழுதப்பட்ட coroutines மற்றும் Emterpreter க்கு ஏறக்குறைய அதே API செயல்படுத்தப்பட்டது, நீங்கள் அவற்றை இணைக்க வேண்டும்).

இந்த நேரத்தில், WASM இல் தொகுக்கப்பட்ட குறியீட்டைப் பிரித்து, எம்டர்பிரெட்டரைப் பயன்படுத்தி விளக்கமளிக்க என்னால் இன்னும் முடியவில்லை, எனவே தொகுதி சாதனங்கள் இன்னும் இயங்கவில்லை (அவர்கள் சொல்வது போல் அடுத்த தொடரில் பார்க்கவும்...). அதாவது, இறுதியில் நீங்கள் இந்த வேடிக்கையான அடுக்கு விஷயத்தைப் பெற வேண்டும்:

  • விளக்கப்பட்ட தொகுதி I/O. சரி, NVMe நேட்டிவ் பெர்ஃபார்மென்ஸுடன் நிஜமாகவே நீங்கள் எதிர்பார்த்தீர்களா? 🙂
  • நிலையான முறையில் தொகுக்கப்பட்ட முக்கிய QEMU குறியீடு (மொழிபெயர்ப்பாளர், பிற முன்மாதிரி சாதனங்கள் போன்றவை)
  • WASM இல் விருந்தினர் குறியீடு மாறும் வகையில் தொகுக்கப்பட்டது

QEMU ஆதாரங்களின் அம்சங்கள்

நீங்கள் ஏற்கனவே யூகித்தபடி, கெஸ்ட் ஆர்கிடெக்சர்களைப் பின்பற்றுவதற்கான குறியீடு மற்றும் ஹோஸ்ட் மெஷின் வழிமுறைகளை உருவாக்குவதற்கான குறியீடு ஆகியவை QEMU இல் பிரிக்கப்பட்டுள்ளன. உண்மையில், இது கொஞ்சம் தந்திரமானது:

  • விருந்தினர் கட்டிடக்கலைகள் உள்ளன
  • இருக்கிறது முடுக்கிகள், அதாவது, லினக்ஸில் ஹார்டுவேர் மெய்நிகராக்கத்திற்கான KVM (விருந்தினர் மற்றும் ஹோஸ்ட் அமைப்புகளுக்கு ஒன்றுக்கொன்று இணக்கமானது), எங்கும் JIT குறியீட்டை உருவாக்குவதற்கான TCG. QEMU 2.9 இல் தொடங்கி, Windows இல் HAXM வன்பொருள் மெய்நிகராக்கத் தரத்திற்கான ஆதரவு தோன்றியது (விவரங்கள்)
  • TCG பயன்படுத்தப்பட்டு வன்பொருள் மெய்நிகராக்கம் இல்லை என்றால், அது ஒவ்வொரு ஹோஸ்ட் கட்டமைப்பிற்கும், உலகளாவிய மொழிபெயர்ப்பாளருக்கும் தனித்தனி குறியீடு உருவாக்க ஆதரவைக் கொண்டுள்ளது.
  • ... மற்றும் இவை அனைத்தையும் சுற்றி - முன்மாதிரியான சாதனங்கள், பயனர் இடைமுகம், இடம்பெயர்வு, ரெக்கார்ட்-ரீப்ளே போன்றவை.

மூலம், உங்களுக்குத் தெரியுமா: QEMU ஆனது முழு கணினியை மட்டுமல்ல, ஹோஸ்ட் கர்னலில் ஒரு தனி பயனர் செயல்முறைக்கான செயலியையும் பின்பற்ற முடியும், எடுத்துக்காட்டாக, பைனரி கருவிக்காக AFL ஃபஸர் மூலம் பயன்படுத்தப்படுகிறது. QEMU இன் இந்த செயல்பாட்டு முறையை யாராவது JS க்கு போர்ட் செய்ய விரும்புவார்களா? 😉

நீண்ட கால இலவச மென்பொருளைப் போலவே, QEMU அழைப்பின் மூலம் கட்டமைக்கப்படுகிறது configure и make. நீங்கள் எதையாவது சேர்க்க முடிவு செய்துள்ளீர்கள் என்று வைத்துக்கொள்வோம்: TCG பின்தளம், நூல் செயல்படுத்தல், வேறு ஏதாவது. Autoconf உடன் தொடர்பு கொள்ளும் வாய்ப்பில் மகிழ்ச்சியாக/திகிலடைய அவசரப்பட வேண்டாம் (பொருத்தமானதாக அடிக்கோடிட்டு) - உண்மையில், configure QEMU கள் வெளிப்படையாக சுயமாக எழுதப்பட்டவை மற்றும் எதிலிருந்தும் உருவாக்கப்படவில்லை.

webassembly

WebAssembly (aka WASM) எனப்படும் இந்த விஷயம் என்ன? இது Asm.jsக்கு மாற்றாக உள்ளது, இனி செல்லுபடியாகும் ஜாவாஸ்கிரிப்ட் குறியீடு போல் நடிக்காது. மாறாக, இது முற்றிலும் பைனரி மற்றும் உகந்ததாக உள்ளது, மேலும் அதில் ஒரு முழு எண்ணை எழுதுவது கூட எளிதானது அல்ல: கச்சிதமாக, இது வடிவத்தில் சேமிக்கப்படுகிறது. LEB128.

Asm.js க்கான ரீலூப்பிங் அல்காரிதம் பற்றி நீங்கள் கேள்விப்பட்டிருக்கலாம் - இது "உயர்-நிலை" செயலாக்க ஓட்டக் கட்டுப்பாட்டு வழிமுறைகளை மீட்டமைப்பதாகும் (அதாவது, இல்லையெனில், லூப்கள் போன்றவை), இதற்காக JS இயந்திரங்கள் வடிவமைக்கப்பட்டுள்ளன, குறைந்த-நிலை LLVM IR இலிருந்து, செயலி மூலம் செயல்படுத்தப்படும் இயந்திரக் குறியீட்டிற்கு நெருக்கமாக உள்ளது. இயற்கையாகவே, QEMU இன் இடைநிலை பிரதிநிதித்துவம் இரண்டாவது நெருக்கமாக உள்ளது. இங்கே அது, பைட்கோட், வேதனையின் முடிவு என்று தோன்றுகிறது ... பின்னர் தொகுதிகள் உள்ளன, என்றால்-இல்லை மற்றும் சுழல்கள்!..

Binaryen பயனுள்ளதாக இருப்பதற்கான மற்றொரு காரணம் இதுவாகும்: WASM இல் சேமிக்கப்படுவதற்கு அருகில் உள்ள உயர் மட்டத் தொகுதிகளை இயற்கையாகவே ஏற்றுக்கொள்ள முடியும். ஆனால் இது அடிப்படை தொகுதிகள் மற்றும் அவற்றுக்கிடையேயான மாற்றங்களின் வரைபடத்திலிருந்து குறியீட்டை உருவாக்க முடியும். சரி, இது வசதியான C/C++ APIக்குப் பின்னால் WebAssembly சேமிப்பக வடிவமைப்பை மறைக்கிறது என்று நான் ஏற்கனவே கூறியுள்ளேன்.

TCG (சிறிய குறியீடு ஜெனரேட்டர்)

TCG முதலில் இருந்தது சி கம்பைலருக்கான பின்தளம்.பின்னர், வெளிப்படையாக, இது GCC உடனான போட்டியைத் தாங்க முடியவில்லை, ஆனால் இறுதியில் அது ஹோஸ்ட் பிளாட்ஃபார்மிற்கான குறியீடு உருவாக்கும் பொறிமுறையாக QEMU இல் அதன் இடத்தைக் கண்டறிந்தது. சில சுருக்கமான பைட்கோடுகளை உருவாக்கும் TCG பின்தளமும் உள்ளது, இது மொழிபெயர்ப்பாளரால் உடனடியாக செயல்படுத்தப்படும், ஆனால் இந்த முறை அதைப் பயன்படுத்துவதைத் தவிர்க்க முடிவு செய்தேன். இருப்பினும், க்யூஇஎம்யூவில் செயல்பாட்டின் மூலம் உருவாக்கப்பட்ட காசநோய்க்கு மாற்றத்தை இயக்குவது ஏற்கனவே சாத்தியமாகும். tcg_qemu_tb_exec, இது எனக்கு மிகவும் பயனுள்ளதாக இருந்தது.

QEMU இல் புதிய TCG பின்தளத்தைச் சேர்க்க, நீங்கள் ஒரு துணை அடைவை உருவாக்க வேண்டும் tcg/<имя архитектуры> (இந்த வழக்கில், tcg/binaryen), மேலும் இது இரண்டு கோப்புகளைக் கொண்டுள்ளது: tcg-target.h и tcg-target.inc.c и பதிவு அது பற்றியது configure. நீங்கள் மற்ற கோப்புகளை அங்கு வைக்கலாம், ஆனால், இந்த இரண்டின் பெயர்களிலிருந்து நீங்கள் யூகிக்க முடிந்தால், அவை இரண்டும் எங்காவது சேர்க்கப்படும்: ஒன்று வழக்கமான தலைப்புக் கோப்பாக (இது சேர்க்கப்பட்டுள்ளது tcg/tcg.h, மற்றும் அது ஏற்கனவே கோப்பகங்களில் உள்ள மற்ற கோப்புகளில் உள்ளது tcg, accel மற்றும் மட்டும் அல்ல), மற்றொன்று - ஒரு குறியீட்டு துணுக்காக மட்டுமே tcg/tcg.c, ஆனால் அதன் நிலையான செயல்பாடுகளுக்கான அணுகல் உள்ளது.

இது எவ்வாறு இயங்குகிறது என்பதைப் பற்றிய விரிவான விசாரணைகளில் அதிக நேரத்தை செலவிடுவேன் என்று முடிவு செய்து, இந்த இரண்டு கோப்புகளின் "எலும்புக்கூடுகளை" மற்றொரு பின்தளத்தில் செயல்படுத்தலில் இருந்து நகலெடுத்தேன், இதை உரிமத் தலைப்பில் நேர்மையாகக் குறிப்பிடுகிறேன்.

கோப்பு tcg-target.h படிவத்தில் முக்கியமாக அமைப்புகளைக் கொண்டுள்ளது #define-கள்:

  • இலக்கு கட்டமைப்பில் எத்தனை பதிவேடுகள் மற்றும் எவ்வளவு அகலம் உள்ளன (எங்களிடம் எவ்வளவு தேவையோ, அவ்வளவு அதிகமாக உள்ளது - "முழுமையான இலக்கு" கட்டமைப்பில் உலாவியால் மிகவும் திறமையான குறியீட்டில் என்ன உருவாக்கப்படும் என்பது பற்றிய கேள்வி அதிகம் ...)
  • புரவலன் அறிவுறுத்தல்களின் சீரமைப்பு: x86 இல், மற்றும் TCI இல் கூட, வழிமுறைகள் சீரமைக்கப்படவில்லை, ஆனால் நான் குறியீட்டு இடையகத்தில் அறிவுறுத்தல்கள் அல்ல, ஆனால் பைனரின் நூலக கட்டமைப்புகளுக்கான சுட்டிகளை வைக்கப் போகிறேன், எனவே நான் கூறுவேன்: 4 பைட்டுகள்
  • பின்தளத்தில் என்ன விருப்ப வழிமுறைகளை உருவாக்க முடியும் - பைனரினில் நாம் காணும் அனைத்தையும் உள்ளடக்குகிறோம், மீதமுள்ளவற்றை முடுக்கி எளிமையானதாக உடைக்கட்டும்
  • பின்தளத்தில் கோரப்பட்ட TLB தற்காலிக சேமிப்பின் தோராயமான அளவு என்ன. உண்மை என்னவென்றால், QEMU இல் எல்லாம் தீவிரமானது: விருந்தினர் MMU ஐ கணக்கில் எடுத்துக்கொண்டு சுமை/கடையைச் செய்யும் உதவி செயல்பாடுகள் இருந்தாலும் (அது இல்லாமல் இப்போது நாம் எங்கே இருப்போம்?), அவை அவற்றின் மொழிபெயர்ப்பு தற்காலிக சேமிப்பை ஒரு கட்டமைப்பின் வடிவத்தில் சேமிக்கின்றன, இதன் செயலாக்கம் நேரடியாக ஒளிபரப்புத் தொகுதிகளில் உட்பொதிக்க வசதியானது. சிறிய மற்றும் வேகமான கட்டளைகளின் மூலம் இந்த கட்டமைப்பில் எந்த ஆஃப்செட் மிகவும் திறமையாக செயலாக்கப்படுகிறது என்பது கேள்வி.
  • இங்கே நீங்கள் ஒன்று அல்லது இரண்டு முன்பதிவு செய்யப்பட்ட பதிவேடுகளின் நோக்கத்தை மாற்றியமைக்கலாம், ஒரு செயல்பாட்டின் மூலம் TB ஐ அழைப்பதை இயக்கலாம் மற்றும் விருப்பமாக இரண்டு சிறியவற்றை விவரிக்கலாம் inline- போன்ற செயல்பாடுகள் flush_icache_range (ஆனால் இது எங்கள் வழக்கு அல்ல)

கோப்பு tcg-target.inc.c, நிச்சயமாக, அளவு மிகவும் பெரியது மற்றும் பல கட்டாய செயல்பாடுகளைக் கொண்டுள்ளது:

  • துவக்கம், எந்த ஆபரேண்டுகளில் எந்த வழிமுறைகள் செயல்படலாம் என்பதற்கான கட்டுப்பாடுகள் உட்பட. வேறொரு பின்தளத்திலிருந்து என்னால் அப்பட்டமாக நகலெடுக்கப்பட்டது
  • ஒரு உள் பைட்கோட் அறிவுறுத்தலை எடுக்கும் செயல்பாடு
  • நீங்கள் இங்கே துணை செயல்பாடுகளை வைக்கலாம், மேலும் நிலையான செயல்பாடுகளையும் பயன்படுத்தலாம் tcg/tcg.c

என்னைப் பொறுத்தவரை, நான் பின்வரும் மூலோபாயத்தைத் தேர்ந்தெடுத்தேன்: அடுத்த மொழிபெயர்ப்புத் தொகுதியின் முதல் வார்த்தைகளில், நான் நான்கு சுட்டிகளை எழுதினேன்: ஒரு தொடக்கக் குறி (அருகில் ஒரு குறிப்பிட்ட மதிப்பு 0xFFFFFFFF, இது TB இன் தற்போதைய நிலையை தீர்மானித்தது), சூழல், உருவாக்கப்பட்ட தொகுதி மற்றும் பிழைத்திருத்தத்திற்கான மேஜிக் எண். முதலில் குறி வைக்கப்பட்டது 0xFFFFFFFF - nஅங்கு n - ஒரு சிறிய நேர்மறை எண், ஒவ்வொரு முறையும் மொழிபெயர்ப்பாளர் மூலம் செயல்படுத்தப்படும் போது அது 1 ஆக அதிகரித்தது. 0xFFFFFFFE, தொகுப்பு நடந்தது, தொகுதி செயல்பாட்டு அட்டவணையில் சேமிக்கப்பட்டது, ஒரு சிறிய "லாஞ்சரில்" இறக்குமதி செய்யப்பட்டது, அதில் இருந்து செயல்படுத்தப்பட்டது tcg_qemu_tb_exec, மற்றும் தொகுதி QEMU நினைவகத்திலிருந்து அகற்றப்பட்டது.

கிளாசிக்ஸை சுருக்கமாகச் சொல்வதானால், "ஊன்றுகோல், புரோக்கரின் இதயத்திற்கு இந்த ஒலியில் எவ்வளவு பின்னிப்பிணைந்துள்ளது...". இருப்பினும், நினைவு எங்கோ கசிந்து கொண்டிருந்தது. மேலும், இது QEMU ஆல் நிர்வகிக்கப்பட்ட நினைவகம்! என்னிடம் ஒரு குறியீடு இருந்தது, அடுத்த அறிவுறுத்தலை எழுதும்போது (சரி, அதாவது, ஒரு சுட்டிக்காட்டி), இந்த இடத்தில் முன்பு இருந்த இணைப்பை நீக்கியது, ஆனால் இது உதவவில்லை. உண்மையில், எளிமையான வழக்கில், QEMU தொடக்கத்தில் நினைவகத்தை ஒதுக்குகிறது மற்றும் அங்கு உருவாக்கப்பட்ட குறியீட்டை எழுதுகிறது. இடையகம் தீர்ந்துவிட்டால், குறியீடு வெளியே எறியப்பட்டு அடுத்தது அதன் இடத்தில் எழுதத் தொடங்குகிறது.

குறியீட்டைப் படித்த பிறகு, மேஜிக் எண்ணைக் கொண்ட தந்திரம், முதல் பாஸிலேயே தொடங்கப்படாத பஃபரில் ஏதேனும் தவறுகளை விடுவிப்பதன் மூலம் குவியல் அழிவில் தோல்வியடையாமல் இருக்க அனுமதித்தது என்பதை உணர்ந்தேன். ஆனால் எனது செயல்பாட்டைப் பிற்பாடு புறக்கணிக்க இடையகத்தை மீண்டும் எழுதுபவர் யார்? எம்ஸ்கிரிப்டன் டெவலப்பர்கள் ஆலோசனையின்படி, நான் ஒரு சிக்கலில் சிக்கியபோது, ​​அதன் விளைவாக வரும் குறியீட்டை சொந்த பயன்பாட்டிற்கு மீண்டும் அனுப்பினேன், அதில் Mozilla Record-Replay-ஐ அமைத்தேன்... பொதுவாக, முடிவில் நான் ஒரு எளிய விஷயத்தை உணர்ந்தேன்: ஒவ்வொரு தொகுதிக்கும், அ struct TranslationBlock அதன் விளக்கத்துடன். எங்கே என்று யூகிக்கவும்... அது சரி, இடையகத்தில் உள்ள பிளாக்கிற்கு சற்று முன். இதை உணர்ந்து, ஊன்றுகோல்களைப் பயன்படுத்துவதை விட்டுவிட முடிவு செய்தேன் (குறைந்தது சில), மற்றும் வெறுமனே மேஜிக் எண்ணை வெளியே எறிந்துவிட்டு, மீதமுள்ள வார்த்தைகளை மாற்றினேன். struct TranslationBlock, தனித்தனியாக இணைக்கப்பட்ட பட்டியலை உருவாக்குகிறது, இது மொழிபெயர்ப்பு தற்காலிக சேமிப்பை மீட்டமைக்கும்போது விரைவாகப் பயணிக்க முடியும், மேலும் நினைவகத்தை விடுவிக்கிறது.

சில ஊன்றுகோல்கள் உள்ளன: எடுத்துக்காட்டாக, குறியீடு இடையகத்தில் குறிக்கப்பட்ட சுட்டிகள் - அவற்றில் சில எளிமையானவை BinaryenExpressionRef, அதாவது, உருவாக்கப்பட்ட அடிப்படைத் தொகுதியில் நேர்கோட்டில் வைக்கப்பட வேண்டிய வெளிப்பாடுகளை அவர்கள் பார்க்கிறார்கள், பகுதி BB களுக்கு இடையில் மாறுவதற்கான நிபந்தனை, பகுதி எங்கு செல்ல வேண்டும். சரி, Relooper க்கான ஏற்கனவே தயாரிக்கப்பட்ட தொகுதிகள் உள்ளன, அவை நிபந்தனைகளுக்கு ஏற்ப இணைக்கப்பட வேண்டும். அவற்றை வேறுபடுத்துவதற்கு, அவை அனைத்தும் குறைந்தது நான்கு பைட்டுகளால் சீரமைக்கப்பட்டுள்ளன என்ற அனுமானம் பயன்படுத்தப்படுகிறது, எனவே நீங்கள் லேபிளுக்கு குறைந்தபட்சம் குறிப்பிடத்தக்க இரண்டு பிட்களைப் பாதுகாப்பாகப் பயன்படுத்தலாம், தேவைப்பட்டால் அதை அகற்ற நினைவில் கொள்ள வேண்டும். TCG லூப்பில் இருந்து வெளியேறுவதற்கான காரணத்தைக் குறிப்பிட, QEMU இல் இத்தகைய லேபிள்கள் ஏற்கனவே பயன்படுத்தப்பட்டுள்ளன.

Binaryen ஐப் பயன்படுத்துதல்

WebAssembly இல் உள்ள தொகுதிகள் செயல்பாடுகளைக் கொண்டிருக்கின்றன, அவை ஒவ்வொன்றிலும் ஒரு உடல் உள்ளது, இது ஒரு வெளிப்பாடு ஆகும். வெளிப்பாடுகள் தனித்தன்மை மற்றும் பைனரி செயல்பாடுகள், பிற வெளிப்பாடுகளின் பட்டியல்களைக் கொண்ட தொகுதிகள், கட்டுப்பாடு ஓட்டம் போன்றவை. நான் ஏற்கனவே கூறியது போல், இங்கே கட்டுப்பாட்டு ஓட்டம் உயர் மட்ட கிளைகள், சுழல்கள், செயல்பாட்டு அழைப்புகள் போன்றவற்றின் மூலம் துல்லியமாக ஒழுங்கமைக்கப்பட்டுள்ளது. செயல்பாடுகளுக்கான வாதங்கள் அடுக்கில் அனுப்பப்படுவதில்லை, ஆனால் வெளிப்படையாக, JS இல் உள்ளதைப் போலவே. உலகளாவிய மாறிகளும் உள்ளன, ஆனால் நான் அவற்றைப் பயன்படுத்தவில்லை, எனவே அவற்றைப் பற்றி நான் உங்களுக்குச் சொல்ல மாட்டேன்.

செயல்பாடுகளில் பூஜ்ஜியத்திலிருந்து எண்ணப்பட்ட உள்ளூர் மாறிகள் உள்ளன, வகை: int32 / int64 / float / double. இந்த வழக்கில், முதல் n உள்ளூர் மாறிகள் செயல்பாட்டிற்கு அனுப்பப்படும் வாதங்கள். கட்டுப்பாட்டு ஓட்டத்தின் அடிப்படையில் இங்குள்ள அனைத்தும் முற்றிலும் குறைந்த அளவில் இல்லை என்றாலும், முழு எண்கள் இன்னும் "கையொப்பமிடப்பட்ட/கையொப்பமிடப்படாத" பண்புக்கூறைக் கொண்டிருக்கவில்லை: எண் எவ்வாறு செயல்படுகிறது என்பது செயல்பாட்டுக் குறியீட்டைப் பொறுத்தது.

பொதுவாக, Binaryen வழங்குகிறது எளிய C-API: நீங்கள் ஒரு தொகுதியை உருவாக்குகிறீர்கள், அவனில் வெளிப்பாடுகளை உருவாக்கவும் - யூனரி, பைனரி, பிற வெளிப்பாடுகளிலிருந்து தொகுதிகள், கட்டுப்பாடு ஓட்டம் போன்றவை. பின்னர் நீங்கள் ஒரு செயல்பாட்டை அதன் உடலாக உருவாக்குகிறீர்கள். நீங்கள், என்னைப் போலவே, குறைந்த நிலை மாற்ற வரைபடத்தை வைத்திருந்தால், ரீலூப்பர் கூறு உங்களுக்கு உதவும். நான் புரிந்து கொண்டவரை, ஒரு தொகுதியின் எல்லைக்கு அப்பால் செல்லாத வரை, ஒரு தொகுதியில் செயல்படுத்தும் ஓட்டத்தின் உயர் மட்டக் கட்டுப்பாட்டைப் பயன்படுத்த முடியும் - அதாவது, உள் வேகமான பாதையை / மெதுவாக உருவாக்க முடியும். உள்ளமைக்கப்பட்ட TLB கேச் செயலாக்கக் குறியீட்டின் உள்ளே பாதை கிளைகள், ஆனால் "வெளிப்புற" கட்டுப்பாட்டு ஓட்டத்தில் குறுக்கிடக்கூடாது . நீங்கள் ஒரு ரீலூப்பரை விடுவிக்கும் போது, ​​அதன் தொகுதிகள் விடுவிக்கப்படும்; நீங்கள் ஒரு தொகுதியை விடுவிக்கும் போது, ​​அதற்கு ஒதுக்கப்பட்ட வெளிப்பாடுகள், செயல்பாடுகள் போன்றவை மறைந்துவிடும். அரங்கம்.

இருப்பினும், தேவையற்ற மொழிபெயர்ப்பாளர் நிகழ்வை உருவாக்குதல் மற்றும் நீக்குதல் இல்லாமல், நீங்கள் பறக்கும்போது குறியீட்டை விளக்க விரும்பினால், இந்த தர்க்கத்தை ஒரு C++ கோப்பில் வைத்து, நூலகத்தின் முழு C++ API ஐயும் நேரடியாக நிர்வகித்தல், தயார்- போர்வைகள் செய்தார்.

எனவே உங்களுக்கு தேவையான குறியீட்டை உருவாக்க

// настроить глобальные параметры (можно поменять потом)
BinaryenSetAPITracing(0);

BinaryenSetOptimizeLevel(3);
BinaryenSetShrinkLevel(2);

// создать модуль
BinaryenModuleRef MODULE = BinaryenModuleCreate();

// описать типы функций (как создаваемых, так и вызываемых)
helper_type  BinaryenAddFunctionType(MODULE, "helper-func", BinaryenTypeInt32(), int32_helper_args, ARRAY_SIZE(int32_helper_args));
// (int23_helper_args приоб^Wсоздаются отдельно)

// сконструировать супер-мега выражение
// ... ну тут уж вы как-нибудь сами :)

// потом создать функцию
BinaryenAddFunction(MODULE, "tb_fun", tb_func_type, func_locals, FUNC_LOCALS_COUNT, expr);
BinaryenAddFunctionExport(MODULE, "tb_fun", "tb_fun");
...
BinaryenSetMemory(MODULE, (1 << 15) - 1, -1, NULL, NULL, NULL, NULL, NULL, 0, 0);
BinaryenAddMemoryImport(MODULE, NULL, "env", "memory", 0);
BinaryenAddTableImport(MODULE, NULL, "env", "tb_funcs");

// запросить валидацию и оптимизацию при желании
assert (BinaryenModuleValidate(MODULE));
BinaryenModuleOptimize(MODULE);

... நான் எதையும் மறந்திருந்தால், மன்னிக்கவும், இது அளவைக் குறிக்கும், மேலும் விவரங்கள் ஆவணத்தில் உள்ளன.

இப்போது கிராக்-ஃபெக்ஸ்-பெக்ஸ் தொடங்குகிறது, இது போன்றது:

static char buf[1 << 20];
BinaryenModuleOptimize(MODULE);
BinaryenSetMemory(MODULE, 0, -1, NULL, NULL, NULL, NULL, NULL, 0, 0);
int sz = BinaryenModuleWrite(MODULE, buf, sizeof(buf));
BinaryenModuleDispose(MODULE);
EM_ASM({
  var module = new WebAssembly.Module(new Uint8Array(wasmMemory.buffer, $0, $1));
  var fptr = $2;
  var instance = new WebAssembly.Instance(module, {
      'env': {
          'memory': wasmMemory,
          // ...
      }
  );
  // и вот уже у вас есть instance!
}, buf, sz);

QEMU மற்றும் JS உலகங்களை எப்படியாவது இணைக்கவும், அதே நேரத்தில் தொகுக்கப்பட்ட செயல்பாடுகளை விரைவாக அணுகவும், ஒரு வரிசை உருவாக்கப்பட்டது (லாஞ்சரில் இறக்குமதி செய்வதற்கான செயல்பாடுகளின் அட்டவணை), மேலும் உருவாக்கப்பட்ட செயல்பாடுகள் அங்கு வைக்கப்பட்டன. குறியீட்டை விரைவாகக் கணக்கிட, பூஜ்ஜிய சொல் மொழிபெயர்ப்புத் தொகுதியின் குறியீடு ஆரம்பத்தில் பயன்படுத்தப்பட்டது, ஆனால் பின்னர் இந்த சூத்திரத்தைப் பயன்படுத்தி கணக்கிடப்பட்ட குறியீடு புலத்தில் வெறுமனே பொருந்தத் தொடங்கியது. struct TranslationBlock.

மூலம், டெமோ (தற்போது இருண்ட உரிமத்துடன்) பயர்பாக்ஸில் மட்டுமே நன்றாக வேலை செய்கிறது. குரோம் டெவலப்பர்கள் எப்படியோ தயாராக இல்லை யாரோ ஒருவர் WebAssembly தொகுதிகளின் ஆயிரத்திற்கும் மேற்பட்ட நிகழ்வுகளை உருவாக்க விரும்புகிறார்கள், எனவே அவர்கள் ஒவ்வொன்றிற்கும் ஒரு ஜிகாபைட் மெய்நிகர் முகவரி இடத்தை ஒதுக்கினர்...

இப்பொழுது இத்துடன் நிறைவடைகிறது. ஒருவேளை யாராவது ஆர்வமாக இருந்தால் மற்றொரு கட்டுரை இருக்கும். அதாவது, குறைந்தபட்சம் உள்ளது வெறும் பிளாக் சாதனங்களைச் செயல்பட வைக்கும். நேட்டிவ் மாட்யூல் தயாராகும் வரை இதையெல்லாம் செய்யக்கூடிய ஒரு மொழிபெயர்ப்பாளர் இன்னும் இருப்பதால், JS உலகில் வழக்கம் போல், WebAssembly தொகுதிகளின் தொகுப்பை ஒத்திசைவற்றதாக மாற்றுவதும் அர்த்தமுள்ளதாக இருக்கும்.

இறுதியாக ஒரு புதிர்: நீங்கள் 32-பிட் கட்டமைப்பில் பைனரியை தொகுத்துள்ளீர்கள், ஆனால் குறியீடு, நினைவக செயல்பாடுகள் மூலம், பைனரினிலிருந்து, அடுக்கில் எங்காவது அல்லது 2-பிட் முகவரி இடத்தின் மேல் 32 ஜிபியில் வேறு எங்காவது ஏறும். பிரச்சனை என்னவென்றால், பினாரியனின் பார்வையில் இது மிகப் பெரிய விளைவான முகவரியை அணுகுகிறது. இதை எப்படி சுற்றி வருவது?

நிர்வாக வழியில்

நான் இதை சோதிக்கவில்லை, ஆனால் எனது முதல் எண்ணம் "நான் 32-பிட் லினக்ஸை நிறுவினால் என்ன செய்வது?" பின்னர் முகவரி இடத்தின் மேல் பகுதி கர்னலால் ஆக்கிரமிக்கப்படும். எவ்வளவு ஆக்கிரமிக்கப்படும் என்பதுதான் ஒரே கேள்வி: 1 அல்லது 2 ஜிபி.

ஒரு புரோகிராமர் வழியில் (பயிற்சியாளர்களுக்கான விருப்பம்)

முகவரி இடத்தின் மேல் ஒரு குமிழியை ஊதுவோம். அது ஏன் வேலை செய்கிறது என்று எனக்கு புரியவில்லை - அங்கே ஏற்கனவே ஒரு அடுக்கு இருக்க வேண்டும். ஆனால் "நாங்கள் பயிற்சியாளர்கள்: எல்லாம் எங்களுக்கு வேலை செய்கிறது, ஆனால் ஏன் என்று யாருக்கும் தெரியாது..."

// 2gbubble.c
// Usage: LD_PRELOAD=2gbubble.so <program>

#include <sys/mman.h>
#include <assert.h>

void __attribute__((constructor)) constr(void)
{
  assert(MAP_FAILED != mmap(1u >> 31, (1u >> 31) - (1u >> 20), PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
}

... இது Valgrind உடன் இணங்கவில்லை என்பது உண்மைதான், ஆனால், அதிர்ஷ்டவசமாக, Valgrind தானே மிகவும் திறம்பட அனைவரையும் அங்கிருந்து வெளியேற்றுகிறது :)

என்னுடைய இந்த குறியீடு எவ்வாறு செயல்படுகிறது என்பதற்கு யாராவது சிறந்த விளக்கத்தை தருவார்கள்...

ஆதாரம்: www.habr.com

கருத்தைச் சேர்