QEMU.js: اب سنجیدہ اور WASM کے ساتھ

ایک دفعہ میں نے تفریح ​​کا فیصلہ کیا۔ عمل کی الٹ پن کو ثابت کریں۔ اور مشین کوڈ سے JavaScript (زیادہ واضح طور پر Asm.js) بنانے کا طریقہ سیکھیں۔ تجربے کے لیے QEMU کا انتخاب کیا گیا، اور کچھ عرصے بعد حبر پر ایک مضمون لکھا گیا۔ تبصروں میں مجھے WebAssembly میں پروجیکٹ کو دوبارہ بنانے کا مشورہ دیا گیا، اور یہاں تک کہ خود کو چھوڑ دوں تقریبا ختم ہو چکا ہے میں کسی طرح یہ پروجیکٹ نہیں چاہتا تھا... کام چل رہا تھا، لیکن بہت آہستہ، اور اب حال ہی میں اس مضمون میں شائع ہوا تبصرہ موضوع پر "تو یہ سب کیسے ختم ہوا؟" میرے تفصیلی جواب کے جواب میں، میں نے سنا "یہ ایک مضمون کی طرح لگتا ہے۔" ٹھیک ہے، اگر آپ کر سکتے ہیں، ایک مضمون ہو گا. ہو سکتا ہے کسی کو یہ مفید لگے۔ اس سے قاری QEMU کوڈ جنریشن بیک اینڈز کے ڈیزائن کے بارے میں کچھ حقائق سیکھیں گے، ساتھ ہی یہ بھی سیکھیں گے کہ ویب ایپلیکیشن کے لیے جسٹ ان ٹائم کمپائلر کیسے لکھا جائے۔

ٹاسکس

چونکہ میں نے پہلے ہی سیکھ لیا تھا کہ کیسے "کسی نہ کسی طرح" QEMU کو JavaScript میں پورٹ کرنا ہے، اس بار یہ فیصلہ کیا گیا کہ اسے سمجھداری سے کیا جائے اور پرانی غلطیوں کو نہ دہرایا جائے۔

غلطی نمبر ایک: پوائنٹ ریلیز سے برانچ

میری پہلی غلطی اپ اسٹریم ورژن 2.4.1 سے اپنے ورژن کو فورک کرنا تھی۔ پھر یہ مجھے ایک اچھا خیال لگا: اگر پوائنٹ ریلیز موجود ہے، تو یہ شاید سادہ 2.4 سے زیادہ مستحکم ہے، اور اس سے بھی زیادہ برانچ master. اور چونکہ میں نے اپنے کیڑے کی کافی مقدار میں اضافہ کرنے کا منصوبہ بنایا تھا، اس لیے مجھے کسی اور کی ضرورت نہیں تھی۔ غالباً ایسا ہی نکلا۔ لیکن بات یہ ہے کہ: QEMU ساکت نہیں ہے، اور کسی موقع پر انہوں نے تیار کردہ کوڈ کو 10 فیصد تک بہتر کرنے کا اعلان بھی کیا۔ "ہاں، اب میں منجمد ہونے جا رہا ہوں،" میں نے سوچا اور ٹوٹ گیا۔ یہاں ہمیں ایک ڈگریشن کرنے کی ضرورت ہے: QEMU.js کی سنگل تھریڈڈ نوعیت کی وجہ سے اور اس حقیقت کی وجہ سے کہ اصل QEMU ملٹی تھریڈنگ کی عدم موجودگی کو ظاہر نہیں کرتا ہے (یعنی بیک وقت کئی غیر متعلقہ کوڈ راستوں کو چلانے کی صلاحیت، اور نہ صرف "تمام دانا استعمال کریں") اس کے لیے اہم ہے، دھاگوں کے اہم کام جو مجھے باہر سے کال کرنے کے قابل ہونے کے لیے "اسے نکالنا" تھا۔ اس سے انضمام کے دوران کچھ قدرتی مسائل پیدا ہوئے۔ تاہم، حقیقت یہ ہے کہ شاخ سے کچھ تبدیلیاں master، جس کے ساتھ میں نے اپنے کوڈ کو ضم کرنے کی کوشش کی، پوائنٹ ریلیز (اور اس وجہ سے میری برانچ میں) میں بھی چیری کو اٹھایا گیا تھا شاید اس میں بھی سہولت شامل نہ ہوتی۔

عام طور پر، میں نے فیصلہ کیا کہ پروٹوٹائپ کو باہر پھینکنا، اس کو پرزوں کے لیے الگ کرنا اور کسی نئی چیز کی بنیاد پر شروع سے ایک نیا ورژن بنانا اور اب سے master.

غلطی نمبر دو: TLP طریقہ کار

جوہر میں، یہ کوئی غلطی نہیں ہے؛ عام طور پر، یہ "کہاں اور کیسے منتقل ہونا ہے؟" اور عمومی طور پر "کیا ہم وہاں پہنچیں گے؟" دونوں کے بارے میں مکمل غلط فہمی کے حالات میں پروجیکٹ بنانے کی ایک خصوصیت ہے۔ ان حالات میں اناڑی پروگرامنگ ایک جائز آپشن تھا، لیکن، قدرتی طور پر، میں اسے غیر ضروری طور پر دہرانا نہیں چاہتا تھا۔ اس بار میں اسے دانشمندی سے کرنا چاہتا تھا: جوہری کمٹ، شعوری کوڈ میں تبدیلیاں (اور "بے ترتیب حروف کو ایک ساتھ نہ باندھنا جب تک کہ یہ مرتب نہ ہو (انتباہات کے ساتھ)"، جیسا کہ وکی اقتباس کے مطابق، لینس ٹوروالڈس نے ایک بار کسی کے بارے میں کہا تھا) وغیرہ۔

غلطی نمبر تین: فورڈ کو جانے بغیر پانی میں اترنا

میں ابھی تک اس سے مکمل طور پر چھٹکارا حاصل نہیں کر پایا ہوں، لیکن اب میں نے فیصلہ کیا ہے کہ کم از کم مزاحمت کا راستہ اختیار نہیں کیا جائے گا، اور اسے "بالغ ہونے کے ناطے" کرنے کا فیصلہ کیا ہے، یعنی، شروع سے میرا TCG بیک اینڈ لکھیں، تاکہ ایسا نہ ہو۔ بعد میں کہنا پڑے گا، "ہاں، یہ یقیناً آہستہ آہستہ ہے، لیکن میں ہر چیز کو کنٹرول نہیں کر سکتا - اس طرح TCI لکھا جاتا ہے..." اس کے علاوہ، یہ ابتدائی طور پر ایک واضح حل کی طرح لگ رہا تھا میں بائنری کوڈ تیار کرتا ہوں۔. جیسا کہ وہ کہتے ہیں، "گینٹ جمع ہوئے۔у, but not that one”: کوڈ، یقیناً، بائنری ہے، لیکن کنٹرول کو آسانی سے اس میں منتقل نہیں کیا جا سکتا - اسے تالیف کے لیے واضح طور پر براؤزر میں دھکیل دیا جانا چاہیے، جس کے نتیجے میں JS دنیا کی طرف سے ایک خاص چیز نکلتی ہے، جس کی اب بھی ضرورت ہے۔ کہیں بچ جائے. تاہم، عام آر آئی ایس سی آرکیٹیکچرز پر، جہاں تک میں سمجھتا ہوں، ایک عام صورت حال یہ ہے کہ دوبارہ تخلیق شدہ کوڈ کے لیے ہدایات کیشے کو واضح طور پر دوبارہ ترتیب دینے کی ضرورت ہے - اگر یہ ہماری ضرورت نہیں ہے، تو، کسی بھی صورت میں، یہ قریب ہے۔ اس کے علاوہ، اپنی آخری کوشش سے، میں نے سیکھا کہ کنٹرول ٹرانسلیشن بلاک کے وسط میں منتقل ہوتا نظر نہیں آتا، اس لیے ہمیں واقعی کسی بھی آفسیٹ سے تشریح شدہ بائیک کوڈ کی ضرورت نہیں ہے، اور ہم اسے ٹی بی کے فنکشن سے آسانی سے بنا سکتے ہیں۔ .

وہ آئے اور لات ماری۔

اگرچہ میں نے جولائی میں کوڈ کو دوبارہ لکھنا شروع کیا، لیکن ایک جادوئی کِک نے کسی کا دھیان نہیں دیا: عام طور پر GitHub سے خطوط ایشوز اور پل کی درخواستوں کے جوابات کے بارے میں اطلاعات کے طور پر آتے ہیں، لیکن یہاں، اچانک تھریڈ میں ذکر Binaryen بطور qemu پسدید سیاق و سباق میں، "اس نے ایسا کچھ کیا، شاید وہ کچھ کہے گا۔" ہم Emscripten کی متعلقہ لائبریری کو استعمال کرنے کے بارے میں بات کر رہے تھے۔ ثنائی WASM JIT بنانے کے لیے۔ ٹھیک ہے، میں نے کہا کہ آپ کے پاس وہاں Apache 2.0 لائسنس ہے، اور QEMU مجموعی طور پر GPLv2 کے تحت تقسیم کیا گیا ہے، اور وہ زیادہ مطابقت نہیں رکھتے۔ اچانک پتہ چلا کہ لائسنس ہو سکتا ہے۔ اسے کسی طرح ٹھیک کریں (مجھے نہیں معلوم: شاید اسے تبدیل کریں، شاید دوہری لائسنسنگ، شاید کچھ اور...)۔ یقیناً اس نے مجھے خوشی بخشی، کیونکہ اس وقت تک میں اسے قریب سے دیکھ چکا تھا۔ بائنری فارمیٹ WebAssembly، اور میں کسی نہ کسی طرح اداس اور ناقابل فہم تھا۔ ایک لائبریری ایسی بھی تھی جو ٹرانزیشن گراف کے ساتھ بنیادی بلاکس کو کھا جائے گی، بائیک کوڈ تیار کرے گی، اور ضرورت پڑنے پر اسے خود ترجمان میں بھی چلائے گی۔

پھر اور بھی تھا۔ ایک خط QEMU میلنگ لسٹ پر، لیکن یہ اس سوال کے بارے میں زیادہ ہے، "بہرحال اس کی ضرورت کسے ہے؟" اور یہ ہے اچانک، یہ ضروری تھا باہر کر دیا. کم از کم، آپ استعمال کے اس طرح کے امکانات کو اکٹھا کر سکتے ہیں اگر یہ کم یا زیادہ تیزی سے کام کرتا ہے:

  • بغیر کسی تنصیب کے تعلیمی کچھ شروع کرنا
  • iOS پر ورچوئلائزیشن، جہاں، افواہوں کے مطابق، واحد ایپلیکیشن جس کو فلائی پر کوڈ جنریشن کا حق حاصل ہے، وہ ایک JS انجن ہے (کیا یہ سچ ہے؟)
  • منی OS کا مظاہرہ - سنگل فلاپی، بلٹ ان، ہر قسم کے فرم ویئر وغیرہ...

براؤزر رن ٹائم کی خصوصیات

جیسا کہ میں نے پہلے ہی کہا ہے، QEMU ملٹی تھریڈنگ سے منسلک ہے، لیکن براؤزر میں یہ نہیں ہے۔ ٹھیک ہے، یعنی نہیں... پہلے تو یہ بالکل موجود ہی نہیں تھا، پھر ویب ورکرز نمودار ہوئے - جہاں تک میں سمجھتا ہوں، یہ میسج پاسنگ پر مبنی ملٹی تھریڈنگ ہے۔ مشترکہ متغیرات کے بغیر. قدرتی طور پر، مشترکہ میموری ماڈل کی بنیاد پر موجودہ کوڈ کو پورٹ کرتے وقت یہ اہم مسائل پیدا کرتا ہے۔ پھر عوامی دباؤ پر اسے بھی نام سے نافذ کیا گیا۔ SharedArrayBuffers. اسے آہستہ آہستہ متعارف کرایا گیا، انہوں نے مختلف براؤزرز میں اس کے آغاز کا جشن منایا، پھر انہوں نے نئے سال کا جشن منایا، اور پھر میلٹ ڈاؤن... جس کے بعد وہ اس نتیجے پر پہنچے کہ وقت کی پیمائش موٹے یا موٹے، لیکن مشترکہ یادداشت کی مدد سے دھاگہ کاؤنٹر کو بڑھا رہا ہے، یہ سب ایک جیسا ہے۔ یہ بہت درست طریقے سے کام کرے گا. لہذا ہم نے مشترکہ میموری کے ساتھ ملٹی تھریڈنگ کو غیر فعال کردیا۔ ایسا لگتا ہے کہ انہوں نے بعد میں اسے دوبارہ آن کر دیا، لیکن جیسا کہ پہلے تجربے سے واضح ہوا، اس کے بغیر زندگی ہے، اور اگر ایسا ہے تو، ہم ملٹی تھریڈنگ پر انحصار کیے بغیر اسے کرنے کی کوشش کریں گے۔

دوسری خصوصیت اسٹیک کے ساتھ نچلی سطح کی ہیرا پھیری کا ناممکن ہے: آپ آسانی سے نہیں لے سکتے، موجودہ سیاق و سباق کو محفوظ کر سکتے ہیں اور نئے اسٹیک کے ساتھ ایک نئے پر سوئچ نہیں کر سکتے۔ کال اسٹیک کا انتظام جے ایس ورچوئل مشین کے ذریعے کیا جاتا ہے۔ ایسا لگتا ہے، مسئلہ کیا ہے، کیوں کہ ہم نے ابھی بھی سابقہ ​​بہاؤ کو مکمل طور پر دستی طور پر منظم کرنے کا فیصلہ کیا ہے؟ حقیقت یہ ہے کہ QEMU میں بلاک I/O کو کورٹینز کے ذریعے لاگو کیا جاتا ہے، اور یہیں سے کم سطح کے اسٹیک ہیرا پھیری کام آئے گی۔ خوش قسمتی سے، Emscipten میں پہلے سے ہی غیر مطابقت پذیر کارروائیوں کے لیے ایک طریقہ کار موجود ہے، یہاں تک کہ دو: Asyncify и ترجمان. پہلا تیار کردہ JavaScript کوڈ میں نمایاں بلوٹ کے ذریعے کام کرتا ہے اور اب اس کی حمایت نہیں کی جاتی ہے۔ دوسرا موجودہ "درست طریقہ" ہے اور مقامی ترجمان کے لیے بائیک کوڈ جنریشن کے ذریعے کام کرتا ہے۔ یہ یقیناً آہستہ آہستہ کام کرتا ہے، لیکن یہ کوڈ کو پھولا نہیں کرتا۔ سچ ہے، اس میکانزم کے لیے کورٹینز کے لیے تعاون کو آزادانہ طور پر دینا پڑتا تھا (ایسین سیفائی کے لیے پہلے سے ہی کورٹینز لکھے گئے تھے اور ایمٹرپریٹر کے لیے تقریباً ایک ہی API کا نفاذ تھا، آپ کو صرف ان کو جوڑنے کی ضرورت تھی)۔

اس وقت، میں ابھی تک کوڈ کو WASM میں مرتب کردہ ایک میں تقسیم کرنے میں کامیاب نہیں ہوا اور Emterpreter کا استعمال کرتے ہوئے اس کی تشریح کی گئی، اس لیے بلاک ڈیوائسز ابھی تک کام نہیں کرتی ہیں (اگلی سیریز میں دیکھیں، جیسا کہ وہ کہتے ہیں...)۔ یعنی، آخر میں آپ کو اس مضحکہ خیز پرتوں والی چیز کی طرح کچھ ملنا چاہئے:

  • تشریح شدہ بلاک I/O ٹھیک ہے، کیا آپ واقعی مقامی کارکردگی کے ساتھ نقلی NVMe کی توقع کرتے ہیں؟ 🙂
  • جامد طور پر مرتب کردہ مین QEMU کوڈ (مترجم، دیگر ایمولیٹڈ ڈیوائسز وغیرہ)
  • متحرک طور پر مہمان کوڈ کو WASM میں مرتب کیا۔

QEMU ذرائع کی خصوصیات

جیسا کہ آپ شاید پہلے ہی اندازہ لگا چکے ہیں، مہمان فن تعمیر کی تقلید کا کوڈ اور میزبان مشین کی ہدایات تیار کرنے کے کوڈ کو QEMU میں الگ کر دیا گیا ہے۔ درحقیقت، یہ تھوڑا سا مشکل بھی ہے:

  • مہمان فن تعمیر ہیں
  • وہاں ہے ایکسلریٹر، یعنی، لینکس پر ہارڈویئر ورچوئلائزیشن کے لیے KVM (مہمان اور میزبان سسٹمز کے لیے جو ایک دوسرے کے ساتھ مطابقت رکھتے ہیں)، کہیں بھی JIT کوڈ جنریشن کے لیے TCG۔ QEMU 2.9 کے ساتھ شروع کرتے ہوئے، ونڈوز پر HAXM ہارڈویئر ورچوئلائزیشن کے معیار کے لیے سپورٹ ظاہر ہوا (تفصیلات)
  • اگر TCG استعمال کیا جاتا ہے اور ہارڈ ویئر ورچوئلائزیشن نہیں، تو اس میں ہر میزبان فن تعمیر کے ساتھ ساتھ عالمگیر ترجمان کے لیے علیحدہ کوڈ جنریشن سپورٹ ہوتا ہے۔
  • ... اور اس سب کے ارد گرد - ایمولیٹڈ پیری فیرلز، یوزر انٹرفیس، ہجرت، ریکارڈ ری پلے وغیرہ۔

ویسے، کیا آپ جانتے ہیں: QEMU نہ صرف پورے کمپیوٹر کی تقلید کر سکتا ہے، بلکہ میزبان کرنل میں ایک الگ صارف کے عمل کے لیے پروسیسر کی بھی تقلید کر سکتا ہے، جو استعمال کیا جاتا ہے، مثال کے طور پر، بائنری آلات کے لیے AFL فزر کے ذریعے۔ شاید کوئی 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 اسٹارٹ اپ پر میموری کو مختص کرتا ہے اور وہاں تیار کردہ کوڈ لکھتا ہے۔ جب بفر ختم ہوجاتا ہے، کوڈ کو باہر پھینک دیا جاتا ہے اور اس کی جگہ اگلا لکھا جانا شروع ہوتا ہے۔

کوڈ کا مطالعہ کرنے کے بعد، میں نے محسوس کیا کہ جادوئی نمبر کے ساتھ چال نے مجھے پہلے پاس پر غیر شروع شدہ بفر پر کچھ غلط کر کے ڈھیر کی تباہی میں ناکام ہونے کی اجازت دی۔ لیکن بعد میں میرے فنکشن کو نظرانداز کرنے کے لیے بفر کو کون دوبارہ لکھتا ہے؟ جیسا کہ ایم اسکرپٹن ڈویلپرز کا مشورہ ہے، جب مجھے کوئی مسئلہ پیش آیا، میں نے نتیجے میں آنے والے کوڈ کو واپس مقامی ایپلیکیشن پر پورٹ کیا، اس پر موزیلا ریکارڈ ری پلے سیٹ کیا... عام طور پر، آخر میں مجھے ایک سادہ سی بات کا احساس ہوا: ہر بلاک کے لیے، a struct TranslationBlock اس کی تفصیل کے ساتھ۔ اندازہ لگائیں کہ کہاں... یہ ٹھیک ہے، بفر میں بلاک سے بالکل پہلے۔ اس کا احساس کرتے ہوئے، میں نے بیساکھیوں کا استعمال چھوڑنے کا فیصلہ کیا (کم از کم کچھ)، اور صرف جادوئی نمبر پھینک دیا، اور باقی الفاظ کو منتقل کر دیا۔ struct TranslationBlock, ایک واحد لنک شدہ فہرست بنانا جو ترجمہ کیش کو دوبارہ ترتیب دینے پر تیزی سے عبور کیا جا سکتا ہے، اور میموری کو خالی کر سکتا ہے۔

کچھ بیساکھییں باقی ہیں: مثال کے طور پر، کوڈ بفر میں نشان زد پوائنٹر - ان میں سے کچھ سادہ ہیں۔ BinaryenExpressionRef، یعنی، وہ ان تاثرات کو دیکھتے ہیں جن کو تیار کردہ بنیادی بلاک میں لکیری طور پر ڈالنے کی ضرورت ہے، حصہ BBs کے درمیان منتقلی کی شرط ہے، حصہ یہ ہے کہ کہاں جانا ہے۔ ٹھیک ہے، Relooper کے لیے پہلے سے ہی تیار شدہ بلاکس موجود ہیں جنہیں حالات کے مطابق جوڑنے کی ضرورت ہے۔ ان میں فرق کرنے کے لیے، یہ مفروضہ استعمال کیا جاتا ہے کہ وہ سب کم از کم چار بائٹس سے منسلک ہیں، لہذا آپ لیبل کے لیے کم سے کم اہم دو بٹس کو محفوظ طریقے سے استعمال کر سکتے ہیں، اگر ضروری ہو تو آپ کو اسے ہٹانا یاد رکھنا چاہیے۔ ویسے، اس طرح کے لیبلز پہلے ہی QEMU میں TCG لوپ سے باہر نکلنے کی وجہ بتانے کے لیے استعمال ہوتے ہیں۔

Binaryen کا استعمال کرتے ہوئے

WebAssembly میں ماڈیولز فنکشنز پر مشتمل ہوتے ہیں، جن میں سے ہر ایک میں ایک جسم ہوتا ہے، جو کہ ایک اظہار ہوتا ہے۔ ایکسپریشنز یونری اور بائنری آپریشنز ہیں، دوسرے ایکسپریشنز، کنٹرول فلو وغیرہ کی فہرستوں پر مشتمل بلاکس۔ جیسا کہ میں پہلے ہی کہہ چکا ہوں، یہاں کنٹرول کے بہاؤ کو اعلیٰ سطحی شاخوں، لوپس، فنکشن کالز وغیرہ کی طرح ترتیب دیا گیا ہے۔ فنکشنز کے دلائل اسٹیک پر نہیں گزرے ہیں، لیکن واضح طور پر، بالکل جے ایس کی طرح۔ عالمی متغیرات بھی ہیں، لیکن میں نے ان کا استعمال نہیں کیا، اس لیے میں آپ کو ان کے بارے میں نہیں بتاؤں گا۔

فنکشنز میں مقامی متغیرات بھی ہوتے ہیں، جن کی تعداد صفر سے ہوتی ہے، قسم کے: int32/int64/float/double۔ اس صورت میں، پہلے n مقامی متغیرات فنکشن کو بھیجے گئے دلائل ہیں۔ براہ کرم نوٹ کریں کہ اگرچہ یہاں ہر چیز کنٹرول کے بہاؤ کے لحاظ سے مکمل طور پر کم درجے کی نہیں ہے، پھر بھی انٹیجرز میں "دستخط شدہ/غیر دستخط شدہ" وصف نہیں ہے: نمبر کس طرح برتاؤ کرتا ہے اس کا انحصار آپریشن کوڈ پر ہوتا ہے۔

عام طور پر، Binaryen فراہم کرتا ہے سادہ سی-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);

... اگر میں کچھ بھول گیا ہوں تو معذرت، یہ صرف پیمانے کی نمائندگی کرنے کے لیے ہے، اور تفصیلات دستاویزات میں ہیں۔

اور اب 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 بٹ آرکیٹیکچر پر بائنری مرتب کی ہے، لیکن کوڈ، میموری آپریشنز کے ذریعے، بائنرین سے، کہیں اسٹیک پر، یا 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

نیا تبصرہ شامل کریں