QEMU.js: هاڻي سنجيده ۽ WASM سان

هڪ دفعي مون مذاق لاء فيصلو ڪيو عمل جي reversibility ثابت ۽ سکو ته ڪيئن ٺاھيو جاوا اسڪرپٽ (وڌيڪ واضح طور تي، Asm.js) مشين ڪوڊ مان. تجربي لاءِ QEMU چونڊيو ويو، ۽ ڪجهه وقت کان پوءِ حبر تي هڪ مضمون لکيو ويو. تبصرن ۾ مون کي صلاح ڏني وئي هئي ته WebAssembly ۾ پروجيڪٽ کي ٻيهر ٺاهيو، ۽ جيتوڻيڪ پاڻ کي ڇڏي ڏيو لڳ ڀڳ ختم مون کي ڪنهن به طرح پروجيڪٽ نه چاهيو ... ڪم هلي رهيو هو، پر تمام سست، ۽ هاڻي، تازو ئي هن آرٽيڪل ۾ ظاهر ٿيو комментарий موضوع تي "پوءِ اهو سڀ ڪيئن ختم ٿيو؟" منهنجي تفصيلي جواب جي جواب ۾، مون ٻڌو ته "هي هڪ مضمون وانگر آواز آهي." خير، جيڪڏهن توهان ڪري سگهو ٿا، اتي هڪ مضمون ٿيندو. ٿي سگهي ٿو ڪنهن کي اهو مفيد ملندو. ان مان پڙهندڙ ڪجهه حقيقتون سکندو QEMU ڪوڊ جنريشن پٺاڻن جي ڊيزائن بابت، انهي سان گڏ هڪ ويب ايپليڪيشن لاءِ Just-in-Time ڪمپيلر ڪيئن لکجي.

ڪمن

جيئن ته مون اڳ ۾ ئي سکيو هو ته ڪيئن ”ڪنهن نه ڪنهن طرح“ بندرگاهه QEMU کي جاوا اسڪرپٽ، هن ڀيري اهو فيصلو ڪيو ويو ته اهو عقلمنديءَ سان ڪيو وڃي ۽ پراڻين غلطين کي نه ورجايو.

غلطي نمبر XNUMX: پوائنٽ ڇڏڻ کان شاخ

منهنجي پهرين غلطي هئي ته منهنجي ورجن کي اپ اسٽريم ورزن 2.4.1 کان فورڪ ڪيو. پوء اهو مون کي هڪ سٺو خيال محسوس ٿيو: جيڪڏهن پوائنٽ رليز موجود آهي، ته اهو شايد سادو 2.4 کان وڌيڪ مستحڪم آهي، ۽ اڃا به وڌيڪ برانچ master. ۽ جڏھن کان مون منصوبابندي ڪئي آھي پنھنجي ڪيڙن جي مناسب مقدار کي شامل ڪرڻ جي، مون کي ڪنھن ٻئي جي ضرورت نه ھئي. شايد ائين ئي ٿيو آهي. پر هتي ڳالهه اها آهي: QEMU اڃا بيٺو ناهي، ۽ ڪجهه نقطي تي انهن ٺاهيل ڪوڊ کي 10 سيڪڙو بهتر ڪرڻ جو اعلان پڻ ڪيو آهي. "ها، هاڻي مان منجمد ٿيڻ وارو آهيان،" مون سوچيو ۽ ڀڄي ويو. هتي اسان کي هڪ تڪرار ڪرڻ جي ضرورت آهي: QEMU.js جي سنگل ٿريڊ واري نوعيت جي ڪري ۽ حقيقت اها آهي ته اصل QEMU ملٽي ٿريڊنگ جي غير موجودگيءَ جو مطلب نٿو رکي (يعني ڪيترن ئي غير لاڳاپيل ڪوڊ رستا کي هڪ ئي وقت هلائڻ جي صلاحيت، ۽ نه رڳو ”سڀ ڪنيل استعمال ڪريو“) ان لاءِ نازڪ آهي، ٿريڊن جا مکيه ڪم مون کي ”ان کي ڦرڻ“ ڪرڻا پوندا آهن ٻاهران ڪال ڪرڻ جي قابل. هن ضمير دوران ڪجهه قدرتي مسئلا پيدا ڪيا. بهرحال، حقيقت اها آهي ته برانچ مان ڪجهه تبديليون master، جنهن سان مون پنهنجي ڪوڊ کي ضم ڪرڻ جي ڪوشش ڪئي، پوائنٽ رليز ۾ چيري به چونڊيا ويا (۽ انهي ڪري منهنجي برانچ ۾) شايد شايد سهولت شامل نه هجي ها.

عام طور تي، مون فيصلو ڪيو ته اهو اڃا تائين سمجھ ۾ اچي ٿو ته پروٽوٽائپ کي اڇلائي، ان کي حصن لاء ڌار ڪيو وڃي ۽ نئين نسخي جي بنياد تي نئين نسخي جي تعمير ڪريو. master.

غلطي نمبر ٻه: TLP طريقو

جوهر ۾، هي ڪا غلطي ناهي، عام طور تي، اهو صرف هڪ منصوبي ٺاهڻ جي هڪ خاصيت آهي جنهن جي مڪمل غلط فهمي جي حالتن ۾ "ڪٿي ۽ ڪيئن منتقل ٿيڻ؟" ۽ عام طور تي "ڇا اسان اتي پهچي سگهنداسين؟" انهن حالتن ۾ بيڪار پروگرامنگ هڪ جائز اختيار هو، پر، قدرتي طور تي، مون ان کي غير ضروري طور تي ورجائڻ نه چاهيو. هن ڀيري مان ان کي عقلمنديءَ سان ڪرڻ چاهيان ٿو: ايٽمي ڪمٽس، شعوري ڪوڊ تبديليون (۽ نه ”بي ترتيب واري اکرن کي گڏ ڪرڻ جيستائين اهو گڏ نه ٿئي (انتباہ سان)“، جيئن لينس ٽوروالڊس هڪ ڀيرو ڪنهن جي باري ۾ چيو هو، وڪي اقتباس جي مطابق) وغيره.

غلطي نمبر ٽي: فورڊ کي ڄاڻڻ کان سواء پاڻي ۾ وڃڻ

مان اڃا تائين مڪمل طور تي ان مان نجات حاصل نه ڪئي آهي، پر هاڻي مون فيصلو ڪيو آهي ته گهٽ ۾ گهٽ مزاحمت جي رستي تي عمل نه ڪيو وڃي، ۽ اهو ڪرڻ لاء "بالغ جي حيثيت ۾،" يعني، شروع کان منهنجو TCG پس منظر لکندو، جيئن نه بعد ۾ چوڻ لاءِ، ”ها، اهو يقيناً آهي، آهستي آهستي، پر مان هر شيءِ تي ضابطو نه ٿو رکي سگهان- ائين ئي TCI لکيو ويو آهي...“ ان کان علاوه، هي شروعاتي طور تي هڪ واضح حل وانگر لڳي ٿو، بعد ۾ مان بائنري ڪوڊ ٺاهيان ٿو. جيئن چوندا آھن، ”گھنٽ گڏ ٿياу, but not that one”: ڪوڊ، يقيناً، بائنري آهي، پر ڪنٽرول صرف ان ڏانهن منتقل نه ٿو ڪري سگهجي - ان کي واضح طور تي براؤزر ۾ دٻايو وڃي تاليف لاءِ، نتيجي ۾ JS دنيا مان هڪ خاص اعتراض، جنهن کي اڃا تائين گهربل آهي. ڪنهن هنڌ محفوظ ڪيو وڃي. بهرحال، عام RISC آرڪيٽيڪچرز تي، جيتري قدر مان سمجهان ٿو، هڪ عام صورتحال آهي واضح طور تي ٻيهر ٺاهيل ڪوڊ لاءِ هدايتي ڪيش کي ري سيٽ ڪرڻ جي ضرورت آهي - جيڪڏهن اهو نه آهي جيڪو اسان کي گهربل آهي، پوء، ڪنهن به صورت ۾، اهو ويجهو آهي. ان کان علاوه، منهنجي آخري ڪوشش مان، مون اهو سکيو ته ڪنٽرول ٽرانسليشن بلاڪ جي وچ ۾ منتقل ٿيل نظر نٿو اچي، تنهنڪري اسان کي واقعي ڪنهن به آفسٽ مان بائيٽ ڪوڊ جي تشريح جي ضرورت ناهي، ۽ اسان صرف ان کي ٽي بي تي فنڪشن مان ٺاهي سگهون ٿا. .

اهي آيا ۽ ڌڪ هنيا

جيتوڻيڪ مون جولاءِ ۾ ڪوڊ کي ٻيهر لکڻ شروع ڪيو، هڪ جادوءَ جي ڪِڪ اُڀري اُٿي بيٺي: عام طور تي GitHub کان خط اچن ٿا اطلاعن جي جوابن جي حوالي سان مسئلن ۽ پل جي درخواستن جي، پر هتي، اوچتو سلسلي ۾ ذڪر Binaryen هڪ qemu پس منظر جي طور تي ان حوالي سان، ”هن اهڙو ڪجهه ڪيو، شايد هو ڪجهه چوندو. اسان Emscripten جي لاڳاپيل لائبريري استعمال ڪرڻ بابت ڳالهائي رهيا هئاسين بائنري WASM JIT ٺاهڻ لاء. خير، مون چيو ته توهان وٽ هڪ Apache 2.0 لائسنس آهي، ۽ QEMU مجموعي طور تي GPLv2 تحت ورهايو ويو آهي، ۽ اهي بلڪل مطابقت نه آهن. اوچتو اهو ظاهر ٿيو ته هڪ لائسنس ٿي سگهي ٿو ان کي ڪنهن به طرح ٺيڪ ڪريو (مون کي خبر ناهي: شايد ان کي تبديل ڪريو، ٿي سگهي ٿو ٻٽي لائسنس، شايد ٻيو ڪجهه ...). اهو، يقينا، مون کي خوش ڪيو، ڇاڪاڻ ته ان وقت تائين مون اڳ ۾ ئي ويجهي ڏٺو هو بائنري فارميٽ WebAssembly، ۽ مان ڪجهه اداس ۽ سمجھ کان ٻاهر هو. اتي هڪ لائبريري پڻ هئي جيڪا بنيادي بلاڪن کي منتقلي گراف سان کائي ويندي هئي، بائيٽ ڪوڊ ٺاهيندي هئي، ۽ جيڪڏهن ضروري هجي ته ان کي ترجمو ڪندڙ پاڻ ۾ هلائي.

پوء اتي وڌيڪ هو خط QEMU ميلنگ لسٽ تي، پر اهو سوال بابت وڌيڪ آهي، "ڪنهن کي ان جي ضرورت آهي؟" ۽ اهو آهي اوچتو، اهو ظاهر ٿيو ته اهو ضروري هو. گهٽ ۾ گهٽ، توهان استعمال جي هيٺين امڪانن کي گڏ ڪري سگهو ٿا، جيڪڏهن اهو ڪم ڪري ٿو يا گهٽ جلدي:

  • ڪنهن به انسٽاليشن کان سواءِ تعليمي ڪجهه شروع ڪرڻ
  • iOS تي ورچوئلائيزيشن، جتي، افواهون جي مطابق، واحد ايپليڪيشن جنهن کي پرواز تي ڪوڊ پيدا ڪرڻ جو حق آهي JS انجڻ (ڇا اهو سچ آهي؟)
  • mini-OS جو مظاهرو - سنگل فلاپي، بلٽ ان، سڀني قسمن جا فرم ویئر وغيره...

برائوزر رن ٽائم خاصيتون

جيئن مون اڳ ۾ ئي چيو آهي، QEMU ملٽي ٿريڊنگ سان ڳنڍيل آهي، پر برائوزر وٽ اهو ناهي. خير، اهو آهي، نه... پهرين ته اهو موجود ئي نه هو، پوءِ ويب ورڪرز ظاهر ٿيا - جيتري قدر مان سمجهان ٿو، اهو ملٽي ٿريڊنگ آهي پيغام جي گذرڻ جي بنياد تي گڏيل متغير کان سواء. قدرتي طور تي، هي اهم مسئلا پيدا ڪري ٿو جڏهن موجوده ڪوڊ کي پورٽ ڪندي گڏيل ميموري ماڊل جي بنياد تي. پوءِ عوامي دٻاءُ تحت ان کي به نالي ماتر لاڳو ڪيو ويو SharedArrayBuffers. اهو آهستي آهستي متعارف ڪرايو ويو، انهن مختلف برائوزرن ۾ ان جي لانچ جو جشن ملهايو، پوءِ انهن نئين سال جو جشن ملهايو، ۽ پوءِ ميلٽ ڊائون... جنهن کان پوءِ اهي ان نتيجي تي پهتا ته وقت جي ماپ کي گهٽ يا ٿلهو ڪيو وڃي، پر گڏيل ياداشت جي مدد سان. ڪائونٽر کي وڌائڻ واري سلسلي، اهو سڀ ڪجهه ساڳيو آهي اهو بلڪل صحيح طور تي ڪم ڪندو. تنهنڪري اسان گڏيل ميموري سان ملٽي ٿريڊنگ کي بند ڪيو. اهو لڳي ٿو ته انهن بعد ۾ ان کي واپس ڦيرايو، پر، جيئن اهو واضح ٿيو ته پهرين تجربي کان، اتي زندگي آهي ان کان سواء، ۽ جيڪڏهن ائين آهي، ته اسين ڪوشش ڪنداسين ته ان کي ملٽي ٿريڊنگ تي ڀروسو ڪرڻ کان سواءِ.

ٻيو خصوصيت اسٽيڪ سان گھٽ سطح جي ڦيرڦار جو ناممڪن آهي: توهان آساني سان نه ٿا وٺي سگهو، موجوده حوالي سان محفوظ ڪريو ۽ هڪ نئين اسٽيڪ سان نئين کي تبديل ڪريو. ڪال اسٽيڪ JS ورچوئل مشين پاران منظم ڪيو ويندو آهي. اهو لڳي ٿو، مسئلو ڇا آهي، ڇو ته اسان اڃا تائين اڳئين وهڪري کي مڪمل طور تي دستي طور تي منظم ڪرڻ جو فيصلو ڪيو آهي؟ حقيقت اها آهي ته QEMU ۾ بلاڪ I/O کي ڪوروٽين ذريعي لاڳو ڪيو ويو آهي، ۽ اهو آهي جتي گهٽ-سطح اسٽيڪ ميپيپليشنز هٿ ۾ ايندا. خوشقسمتيء سان، Emscipten اڳ ۾ ئي هڪ ميڪانيزم تي مشتمل آهي غير مطابقت واري عملن لاء، جيتوڻيڪ ٻه: Asyncify и ترجمان. پهرين هڪ ٺاهيل جاوا اسڪرپٽ ڪوڊ ۾ اهم بلوٽ ذريعي ڪم ڪري ٿو ۽ هاڻي سپورٽ ناهي. ٻيو موجوده "صحيح رستو" آهي ۽ ڪم ڪري ٿو بائيٽ ڪوڊ نسل جي ذريعي ڏيهي مترجم لاءِ. اهو ڪم ڪري ٿو، يقينا، سست، پر اهو ڪوڊ کي ڦٽو نٿو ڪري. سچ پچ، هن ميکانيزم لاءِ ڪورٽينز جي مدد کي آزاديءَ سان حصو ڏيڻو پوندو هو (اتي اڳ ۾ ئي ڪوروٽينون Asyncify لاءِ لکيل هيون ۽ اتي لڳ ڀڳ ساڳي API جو عمل هو ايمٽرپريٽر لاءِ، توهان کي صرف انهن کي ڳنڍڻ جي ضرورت آهي).

هن وقت، مان اڃا تائين ڪوڊ کي ورهائڻ جو انتظام نه ڪيو آهي هڪ WASM ۾ مرتب ڪيل ۽ ايمٽرپريٽر استعمال ڪندي تفسير، تنهنڪري بلاڪ ڊوائيس اڃا تائين ڪم نه ڪندا آهن (ايندڙ سيريز ۾ ڏسو، جيئن اهي چون ٿا ...). اهو آهي، آخر ۾ توهان کي ڪجهه حاصل ڪرڻ گهرجي جهڙوڪ هن عجيب پرت واري شيء:

  • تفسير ٿيل بلاڪ I/O. خير، ڇا توهان واقعي جي توقع ڪئي هئي NVMe جي اصلي ڪارڪردگي سان؟ 🙂
  • statically مرتب ڪيل مکيه QEMU ڪوڊ (مترجم، ٻيون نقلي ڊوائيسز، وغيره)
  • متحرڪ طور تي مرتب ڪيل مهمان ڪوڊ WASM ۾

QEMU ذريعن جون خاصيتون

جئين توهان شايد اڳ ۾ ئي اندازو لڳايو آهي، ڪوڊ گيسٽ آرڪيٽيڪچرز کي نقل ڪرڻ ۽ ميزبان مشين جي هدايتون ٺاهڻ لاء ڪوڊ QEMU ۾ الڳ ٿيل آهن. حقيقت ۾، اهو اڃا به ٿورو مشڪل آهي:

  • مهمان فن تعمير آهن
  • آهي تيز ڪندڙ، يعني، لينڪس تي هارڊويئر ورچوئلائيزيشن لاءِ KVM (مهمان ۽ ميزبان سسٽم لاءِ هڪ ٻئي سان مطابقت رکندڙ)، TCG لاءِ JIT ڪوڊ جنريشن ڪٿي به. QEMU 2.9 سان شروع ڪندي، ونڊوز تي HAXM هارڊويئر ورچوئلائيزيشن معيار لاءِ سپورٽ ظاهر ٿيو (تفصيلات)
  • جيڪڏهن TCG استعمال ٿيل آهي ۽ هارڊويئر ورچوئلائيزيشن نه آهي، ته پوءِ ان ۾ هر ميزبان آرڪيٽيڪچر لاءِ الڳ ڪوڊ جنريشن سپورٽ آهي، انهي سان گڏ يونيورسل مترجم لاءِ
  • ... ۽ ان جي چوڌاري سڀ - emulated peripherals, user interface, migration, record-replay, etc.

رستي ۾، توهان کي خبر آهي: QEMU نه رڳو پوري ڪمپيوٽر کي ايميوليٽ ڪري سگھي ٿو، پر پروسيسر کي به الڳ يوزر پروسيس لاءِ ميزبان ڪرنل ۾، جيڪو استعمال ڪيو ويندو آهي، مثال طور، بائنري اوزارن لاءِ AFL fuzzer ذريعي. شايد ڪو ماڻهو QEMU جي آپريشن جي هن موڊ کي JS ڏانهن پورٽ ڪرڻ چاهيندو؟ 😉

تمام گهڻي عرصي کان مفت سافٽ ويئر وانگر، QEMU ڪال ذريعي ٺهيل آهي configure и make. اچو ته چئو ته توهان ڪجهه شامل ڪرڻ جو فيصلو ڪيو: هڪ TCG پس منظر، موضوع تي عمل درآمد، ٻيو ڪجهه. Autoconf سان ڳالھ ٻولھ جي امڪان تي خوش ٿيڻ / خوفزده ٿيڻ لاءِ تڪڙ نه ڪريو (مناسب طور ھيٺ لکو) - حقيقت ۾، configure QEMU جي ظاهري طور تي خود لکيل آهي ۽ ڪنهن به شيء مان پيدا نه ڪيو ويو آهي.

ويب ايزازي

پوءِ هي شيءِ ڇا آهي WebAssembly (اڪا WASM)؟ هي Asm.js جو متبادل آهي، هاڻي صحيح جاوا اسڪرپٽ ڪوڊ هجڻ جي دعويٰ نٿو ڪري. ان جي برعڪس، اهو خالص طور تي بائنري ۽ اصلاحي آهي، ۽ اڃا به صرف ان ۾ هڪ انٽيجر لکڻ بلڪل سادو ناهي: جامعيت لاء، اهو فارميٽ ۾ ذخيرو ٿيل آهي. ايل بي 128.

توهان شايد Asm.js لاءِ ريلوپنگ الگورٿم بابت ٻڌو هوندو - هي آهي “هاءِ-ليول” وهڪري جي ڪنٽرول جي هدايتن جي بحالي (جيڪو آهي، پوءِ-ٻيو، لوپس، وغيره)، جنهن لاءِ JS انجڻ ٺهيل آهن، کان گھٽ-سطح LLVM IR، پروسيسر پاران جاري ڪيل مشين ڪوڊ جي ويجهو. قدرتي طور، QEMU جي وچولي نمائندگي سيڪنڊ جي ويجهو آهي. اهو لڳي ٿو ته هتي اهو آهي، بائيٽ ڪوڊ، عذاب جي پڄاڻي... ۽ پوءِ اتي بلاڪ آهن، جيڪڏهن-تو-ٻيو ۽ لوپ! ..

۽ اهو هڪ ٻيو سبب آهي ته Binaryen مفيد آهي: اهو قدرتي طور تي اعلي سطحي بلاڪ کي قبول ڪري سگهي ٿو جيڪو WASM ۾ محفوظ ڪيو ويندو. پر اهو پڻ ڪوڊ ٺاهي سگھي ٿو بنيادي بلاڪ جي گراف مان ۽ انهن جي وچ ۾ منتقلي. خير، مون اڳ ۾ ئي چيو آهي ته اهو WebAssembly اسٽوريج فارميٽ کي آسان C/C++ API جي پويان لڪائي ٿو.

TCG (ننڍو ڪوڊ جنريٽر)

ٽي سي جي اصل ۾ هو پوءِ، بظاهر، اهو GCC سان مقابلو نه ڪري سگهيو، پر آخر ۾ ان کي QEMU ۾ ميزبان پليٽ فارم لاءِ ڪوڊ جنريشن ميڪانيزم جي طور تي جڳهه ملي. هتي هڪ TCG پس منظر پڻ آهي جيڪو ڪجهه خلاصو بائيٽ ڪوڊ ٺاهي ٿو، جيڪو فوري طور تي مترجم طرفان عمل ڪيو ويو آهي، پر مون هن وقت ان کي استعمال ڪرڻ کان بچڻ جو فيصلو ڪيو. بهرحال، حقيقت اها آهي ته QEMU ۾ اهو اڳ ۾ ئي ممڪن آهي ته فعل ذريعي ٺاهيل ٽي بي جي منتقلي کي چالو ڪيو وڃي. 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-s:

  • ٽارگيٽ آرڪيٽيڪچر تي ڪيترا رجسٽر ۽ ڪهڙي ويڪر موجود آهي (اسان وٽ جيترا آهن، جيترا اسان چاهيون ٿا، جيترا اسان چاهيون ٿا - سوال وڌيڪ اهو آهي ته ”مڪمل ٽارگيٽ“ آرڪيٽيڪچر تي برائوزر طرفان وڌيڪ ڪارائتو ڪوڊ ڇا ٺاهيو ويندو. ...)
  • ميزبان جي هدايتن جي ترتيب: x86 تي، ۽ ايستائين جو TCI ۾، هدايتون بلڪل به نه هونديون آهن، پر مان ڪوڊ بفر ۾ رکڻ وارو آهيان هدايتون نه، پر Binaryen لائبريري جي جوڙجڪ ڏانهن اشارو، تنهنڪري مان چوندس: 4 بائيٽ
  • ڪهڙو اختياري هدايتون پس منظر ٺاهي سگھي ٿو - اسان بائنريئن ۾ جيڪا شيءِ ڳوليون ٿا ان ۾ شامل ڪريون ٿا، ايڪسليٽر کي ڇڏي ڏيو باقي کي آسانن ۾ ٽوڙيو
  • پس منظر پاران درخواست ڪيل TLB ڪيش جي اندازي مطابق ماپ ڇا آهي. حقيقت اها آهي ته QEMU ۾ سڀ ڪجهه سنجيده آهي: جيتوڻيڪ اتي مددگار ڪم آهن جيڪي لوڊ/اسٽور کي انجام ڏين ٿا مهمان MMU (هاڻي اسان ان کان سواء ڪٿي هوندا؟)، اهي انهن جي ترجمي جي ڪيش کي هڪ ساخت جي صورت ۾ محفوظ ڪن ٿا. جنهن جي پروسيسنگ سڌو سنئون براڊڪاسٽ بلاڪ ۾ شامل ڪرڻ لاءِ آسان آهي. سوال اهو آهي ته هن ڍانچي ۾ ڪهڙي آفسٽ تمام گهڻي موثر طريقي سان ڪمانڊز جي ننڍڙي ۽ تيز ترتيب سان عمل ۾ اچي ٿي؟
  • هتي توهان هڪ يا ٻه محفوظ رجسٽرن جي مقصد کي ٽائيڪ ڪري سگهو ٿا، هڪ فنڪشن ذريعي ٽي بي کي ڪال ڪرڻ کي فعال ڪري سگهو ٿا ۽ اختياري طور تي بيان ڪري سگهو ٿا ڪجهه ننڍڙا inline- افعال جهڙوڪ flush_icache_range (پر اهو اسان جو معاملو ناهي)

فائيل tcg-target.inc.c، يقينا، عام طور تي سائيز ۾ تمام وڏو آهي ۽ ڪيترن ئي لازمي افعال تي مشتمل آهي:

  • شروعات، جنهن تي پابنديون شامل آهن جن تي هدايتون ڪم ڪري سگهن ٿيون. مون کي هڪ ٻئي پس منظر مان بيحد نقل ڪيو ويو آهي
  • فنڪشن جيڪو هڪ اندروني بائيٽ ڪوڊ هدايتون وٺندو آهي
  • توھان ھتي معاون افعال پڻ رکي سگھو ٿا، ۽ توھان پڻ استعمال ڪري سگھو ٿا جامد افعال مان tcg/tcg.c

پنهنجي لاءِ، مون هيٺ ڏنل حڪمت عملي اختيار ڪئي: ايندڙ ترجمي واري بلاڪ جي پهرئين لفظن ۾، مون چار اشارا لکيا: هڪ شروعاتي نشان (واقعي ۾ هڪ خاص قدر 0xFFFFFFFF، جيڪو طئي ڪيو ٽي بي جي موجوده حالت)، حوالي سان، ٺاهيل ماڊل، ۽ ڊيبگنگ لاءِ جادو نمبر. شروعات ۾ نشان لڳايو ويو 0xFFFFFFFF - nڪٿي n - ھڪڙو ننڍڙو مثبت نمبر، ۽ ھر ڀيري ان کي مترجم جي ذريعي عمل ڪيو ويو، اھو وڌايو ويو 1. جڏھن پھچي 0xFFFFFFFE, تاليف ٿي چڪو آهي، ماڊل کي فنڪشن ٽيبل ۾ محفوظ ڪيو ويو، هڪ ننڍڙي "لانچر" ۾ درآمد ڪيو ويو، جنهن ۾ عملدرآمد ٿي ويو tcg_qemu_tb_exec، ۽ ماڊل کي QEMU ياداشت مان هٽايو ويو.

ڪلاسڪس کي بيان ڪرڻ لاءِ، ”ڪرچ، هن آواز ۾ پروگر جي دل لاءِ ڪيترو جڙيل آهي...“. تنهن هوندي به، ياداشت ڪٿي لڪي رهي هئي. ان کان علاوه، اهو QEMU پاران منظم ڪيل ياداشت هئي! مون وٽ هڪ ڪوڊ هو، جڏهن ايندڙ هدايتون (چڱو، اهو آهي، هڪ پوائنٽر) لکندو هو، جنهن جي لنڪ هن جڳهه تي اڳ ۾ هئي، پر اهو مدد نه ڪيو. دراصل، آسان ترين صورت ۾، QEMU شروعاتي تي ميموري مختص ڪري ٿو ۽ اتي ٺاهيل ڪوڊ لکي ٿو. جڏهن بفر ختم ٿئي ٿو، ڪوڊ ڪڍيو ويندو آهي ۽ ان جي جاء تي ايندڙ هڪ لکڻ شروع ٿئي ٿو.

ڪوڊ پڙهڻ کان پوء، مون محسوس ڪيو ته جادو نمبر سان چال مون کي اجازت ڏني ته هيپ تباهي تي ناڪام ٿيڻ جي پهرين پاس تي غير شروع ٿيل بفر تي ڪجهه غلط آزاد ڪندي. پر پوءِ ڪير منهنجي فنڪشن کي نظرانداز ڪرڻ لاءِ بفر کي ٻيهر لکندو؟ جيئن ايم اسڪرپٽ ڊولپرز جي صلاح آهي، جڏهن مون کي ڪنهن مسئلي ۾ پئجي ويو، مون نتيجو ڪوڊ واپس ڏيهي ايپليڪيشن ڏانهن موڪليو، ان تي موزيلا رڪارڊ-ريپلي سيٽ ڪيو... عام طور تي، آخر ۾ مون هڪ سادي شيء محسوس ڪئي: هر بلاڪ لاء، هڪ struct TranslationBlock ان جي وضاحت سان. اندازو لڳايو ته ڪٿي... اهو صحيح آهي، بفر ۾ بلاڪ کان ٿورو اڳ. ان کي محسوس ڪندي، مون فيصلو ڪيو ته بيچيني (گهٽ ۾ گهٽ ڪجهه) استعمال ڪرڻ ڇڏي ڏيو، ۽ صرف جادو نمبر ڪڍي ڇڏيو، ۽ باقي لفظن کي منتقل ڪيو. struct TranslationBlock, ھڪڙي ھڪڙي ڳنڍيل لسٽ ٺاھيو جنھن کي تڪڙو منتقل ڪري سگھجي ٿو جڏھن ترجمو ڪيش ري سيٽ ڪيو وڃي، ۽ ياداشت کي خالي ڪريو.

ڪجھ ڪرچ باقي رھيا آھن: مثال طور، ڪوڊ بفر ۾ نشان لڳل پوائنٽر - انھن مان ڪجھ سادو آھن BinaryenExpressionRef، اهو آهي، اهي انهن اظهارن تي نظر اچن ٿا جن کي لڪير سان ٺهيل بنيادي بلاڪ ۾ رکڻ جي ضرورت آهي، حصو BBs جي وچ ۾ منتقلي لاء شرط آهي، حصو اهو آهي جتي وڃڻو آهي. يقينن، ريلوپر لاء اڳ ۾ ئي تيار ڪيل بلاڪ آهن جيڪي حالتن جي مطابق ڳنڍڻ جي ضرورت آهي. انهن کي فرق ڪرڻ لاءِ، اهو فرض استعمال ڪيو ويو آهي ته اهي سڀئي گهٽ ۾ گهٽ چار بائيٽ سان جڙيل آهن، تنهنڪري توهان محفوظ طور تي گهٽ ۾ گهٽ اهم ٻه بٽ استعمال ڪري سگهو ٿا ليبل لاءِ، توهان کي صرف ياد رکڻ جي ضرورت آهي جيڪڏهن ضروري هجي ته ان کي هٽائڻ لاءِ. رستي ۾، اهڙا ليبل اڳ ۾ ئي استعمال ڪيا ويا آهن QEMU ۾ TCG لوپ مان نڪرڻ جو سبب ظاهر ڪرڻ لاء.

Binaryen استعمال ڪندي

WebAssembly ۾ ماڊلز ۾ فنڪشن شامل آهن، جن مان هر هڪ جسم تي مشتمل آهي، جيڪو هڪ اظهار آهي. ايڪسپريسز unary ۽ binary آپريشن آهن، بلاڪ ٻين ايڪسپريس جي فهرستن تي مشتمل آهن، ڪنٽرول وهڪري، وغيره. جيئن ته مون اڳ ۾ ئي چيو آهي، ڪنٽرول وهڪري هتي منظم ڪيو ويو آهي خاص طور تي اعلي سطحي شاخن، لوپس، فنڪشن ڪال، وغيره. افعال جا دليل اسٽيڪ تي نه گذريا آھن، پر واضح طور تي، جھڙي طرح JS ۾. هتي پڻ عالمي متغير آهن، پر مون انهن کي استعمال نه ڪيو آهي، تنهنڪري مان توهان کي انهن بابت نه ٻڌائيندس.

ڪمن ۾ پڻ مقامي متغير آهن، صفر کان نمبر ڏنل، قسم جو: int32 / int64 / float / double. ھن حالت ۾، پھريون ن مقامي متغير آھن اھي دليل آھن جيڪي فنڪشن ڏانھن گذري ويا آھن. مهرباني ڪري نوٽ ڪريو ته جيتوڻيڪ هتي هر شيءِ ڪنٽرول جي وهڪري جي لحاظ کان مڪمل طور تي گهٽ سطح تي نه آهي، انٽيگرز اڃا تائين "سائن ٿيل/غير دستخط ٿيل" وصف نه کڻندا آهن: نمبر ڪيئن عمل ڪندو آهي آپريشن ڪوڊ تي منحصر آهي.

عام طور تي ڳالهائڻ، Binaryen مهيا ڪري ٿو سادي C-API: توهان هڪ ماڊل ٺاهيو، هن ۾ ايڪسپريس ٺاهي - unary, binary, blocks from other expressions, control flow, etc. پوء توهان هڪ فنڪشن ٺاهي ان جي جسم جي اظهار سان. جيڪڏهن توهان، مون وانگر، هڪ گهٽ سطحي منتقلي گراف آهي، ريلوپر جزو توهان جي مدد ڪندو. جيتري قدر مان سمجهان ٿو، اهو ممڪن آهي ته هڪ بلاڪ ۾ عمل جي وهڪري جي اعلي سطحي ڪنٽرول کي استعمال ڪيو وڃي، جيستائين اهو بلاڪ جي حدن کان ٻاهر نه وڃي - اهو آهي، اهو ممڪن آهي ته اندروني تيز رفتار / سست. بلٽ ان TLB ڪيش پروسيسنگ ڪوڊ اندر path برانچنگ، پر ”بيروني“ ڪنٽرول وهڪري ۾ مداخلت نه ڪرڻ. جڏهن توهان هڪ ريلوپر کي آزاد ڪيو ٿا، ان جا بلاڪ آزاد ٿي ويا آهن؛ جڏهن توهان هڪ ماڊل آزاد ڪيو ٿا، ان ۾ مختص ڪيل اظهار، افعال، وغيره غائب ٿي ويندا آهن. ميدان.

تنهن هوندي، جيڪڏهن توهان ڪنهن مترجم مثال جي غير ضروري تخليق ۽ حذف ڪرڻ کان سواءِ فلائي تي ڪوڊ جي تشريح ڪرڻ چاهيو ٿا، ته اهو سمجھ ۾ اچي سگھي ٿو ته هن منطق کي 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);

...جيڪڏهن مون ڪجھ وساري ڇڏيو، معاف ڪجو، اهو صرف اسڪيل جي نمائندگي ڪرڻ لاءِ آهي، ۽ تفصيل دستاويز ۾ آهن.

۽ هاڻي crack-fex-pex شروع ٿئي ٿو، ڪجهه هن طرح:

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 ماڊلز جي هڪ هزار کان وڌيڪ مثالون ٺاهڻ چاهيندو، تنهن ڪري انهن کي صرف هڪ گيگا بائيٽ مجازي ايڊريس جي جڳهه مختص ڪئي وئي هر هڪ لاء ...

اهو سڀ ڪجهه هاڻي لاءِ آهي. شايد ڪو ٻيو مضمون هوندو جيڪڏهن ڪنهن کي دلچسپي هجي. يعني گهٽ ۾ گهٽ رهي ٿو صرف بلاڪ ڊوائيسز کي ڪم ڪرڻ. WebAssembly ماڊلز جي تاليف کي هم وقت سازي ڪرڻ جو مطلب ٿي سگھي ٿو، جيئن JS دنيا ۾ رواج آهي، ڇو ته اڃا تائين ھڪڙو مترجم آھي جيڪو ھي سڀ ڪجھ ڪري سگھي ٿو جيستائين اصلي ماڊل تيار نه ٿئي.

آخرڪار هڪ معما: توهان 32-bit آرڪيٽيڪچر تي هڪ بائنري مرتب ڪيو آهي، پر ڪوڊ، ميموري آپريشنز ذريعي، بائنري کان چڙهندو، ڪٿي اسٽيڪ تي، يا 2-bit ايڊريس اسپيس جي اپر 32 GB ۾ ڪٿي. مسئلو اهو آهي ته Binaryen جي نقطي نظر کان اهو تمام وڏو نتيجو پتي تائين رسائي آهي. انهي جي چوڌاري ڪيئن حاصل ڪجي؟

منتظم جي طريقي ۾

مون ان جي جاچ ختم نه ڪئي، پر منهنجو پهريون خيال هو ”ڇا جيڪڏهن مان 32-bit لينڪس انسٽال ڪيان؟ پوءِ ايڊريس اسپيس جي مٿئين حصي تي ڪنييل قبضو ڪيو ويندو. صرف سوال اهو آهي ته ڪيترو قبضو ڪيو ويندو: 1 يا 2 Gb.

هڪ پروگرامر جي طريقي سان (عمل ڪندڙن لاءِ اختيار)

اچو ته ايڊريس اسپيس جي چوٽي تي هڪ بلبل کي ڦوڪيون. مان پاڻ نه ٿو سمجهان ته اهو ڇو ڪم ڪري ٿو - اتي اڳ ۾ ئي اتي هڪ اسٽيڪ هجڻ گهرجي. پر ”اسان عملي آهيون: سڀ ڪجهه اسان لاءِ ڪم ڪري ٿو، پر ڪنهن کي به خبر ناهي ڇو...“

// 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

تبصرو شامل ڪريو