Qemu.js د JIT ملاتړ سره: تاسو لاهم کولی شئ منس شاته وګرځوئ

څو کاله دمخه فابریس بیلارډ د jslinux لخوا لیکل شوی د کمپیوټر ایمولیټر په جاواسکریپټ کې لیکل شوی. له هغې وروسته لږ تر لږه نور هم وو مجازی x86. مګر دا ټول، تر هغه ځایه چې زه پوهیږم، ژباړونکي وو، پداسې حال کې چې Qemu، د ورته فابریس بیلارډ لخوا ډیر دمخه لیکل شوي، او شاید، هر ځان ته درناوي عصري ایمولیټر، د کوربه سیسټم کوډ کې د میلمستون کوډ JIT تالیف کاروي. ما ته داسې بریښي چې دا د هغه چا په اړه د مخالف کار پلي کولو وخت دی چې براوزر یې حل کوي: په جاواسکریپټ کې د ماشین کوډ JIT تالیف ، د کوم لپاره چې دا د کیمو بندر ته خورا منطقي ښکاري. داسې ښکاري چې ولې Qemu، دلته ساده او د کاروونکي دوستانه ایمولیټرونه شتون لري - ورته VirtualBox، د بیلګې په توګه - نصب او کار کوي. مګر Qemu څو په زړه پورې ځانګړتیاوې لري

  • خلاص سرچینه
  • د کرنل ډرایور پرته د کار کولو وړتیا
  • د ژباړونکي حالت کې د کار کولو وړتیا
  • د کوربه او میلمنو دواړو جوړښتونو لوی شمیر لپاره ملاتړ

د دریم ټکي په اړه ، زه اوس تشریح کولی شم چې په حقیقت کې ، په TCI حالت کې ، دا پخپله د میلمه ماشین لارښوونې ندي چې تشریح شوي ، مګر د دوی څخه ترلاسه شوي بایټ کوډ ، مګر دا جوهر نه بدلوي - د جوړولو او چلولو لپاره. Qemu په نوي جوړښت کې، که تاسو خوشحاله یاست، د A C کمپیلر کافی دی - د کوډ جنریټر لیکل ځنډول کیدی شي.

او اوس ، زما په وړیا وخت کې د کیمو سرچینې کوډ سره د دوه کلن تفریح ​​​​کولو وروسته ، یو کاري پروټوټایپ څرګند شو ، په کوم کې چې تاسو دمخه چلولی شئ ، د مثال په توګه ، کولیبري OS.

Emscripten څه شی دی؟

نن ورځ ، ډیری تالیف کونکي څرګند شوي ، چې پایله یې جاواسکریپټ دی. ځینې، لکه ټایپ سکریپټ، په اصل کې د ویب لپاره د لیکلو غوره لاره وه. په ورته وخت کې، Emscripten یوه لاره ده چې موجوده C یا C++ کوډ واخلي او په براوزر کې د لوستلو وړ بڼه کې تالیف کړي. پر دا پاڼه موږ د مشهور برنامو ډیری بندرونه راټول کړي دي: دلتهد مثال په توګه، تاسو کولی شئ PyPy ته وګورئ - په لاره کې، دوی ادعا کوي چې دمخه یې JIT لري. په حقیقت کې، هر برنامه په ساده ډول په براوزر کې تالیف او چلول کیدی نشي - یو شمیر شتون لري برخی، کوم چې تاسو باید ورسره ولرئ، په هرصورت، لکه څنګه چې په ورته پاڼه کې لیکل شوي "Emscripten د نږدې هر ډول تالیف کولو لپاره کارول کیدی شي. وړ وړ جاواسکریپټ ته C/C++ کوډ" دا دی، یو شمیر عملیات شتون لري چې د معیار سره سم نه تعریف شوي چلند دي، مګر معمولا په x86 کار کوي - د بیلګې په توګه، متغیراتو ته غیر منظم لاسرسی، چې په عمومي توګه په ځینو جوړښتونو کې منع دی. , Qemu یو کراس پلیټ فارم برنامه ده او ، ما غوښتل باور وکړم ، او دا دمخه ډیر نه تعریف شوي چلند نلري - دا واخلئ او تالیف کړئ ، بیا د JIT سره یو څه ټینکر وکړئ - او تاسو بشپړ شو! مګر دا نه دی قضیه...

لومړی هڅه وکړئ

په عموم کې ، زه لومړی کس نه یم چې جاواسکریپټ ته د کیمو پورټ کولو مفکورې سره راغلم. د ReactOS فورم کې یوه پوښتنه شوې وه که دا د Emscripten په کارولو سره امکان ولري. حتی دمخه ، داسې اوازې وې چې فابریس بیلارډ دا په شخصي ډول کړی و ، مګر موږ د jslinux په اړه خبرې کولې ، کوم چې تر هغه ځایه چې زه پوهیږم ، یوازې یوه هڅه ده چې په لاسي ډول په JS کې کافي فعالیت ترلاسه کړي ، او له سکریچ څخه لیکل شوی و. وروسته، مجازی x86 لیکل شوی و - د دې لپاره ناڅرګندې سرچینې پوسټ شوې، او لکه څنګه چې ویل شوي، د ایمولیشن لوی "ریالیزم" دا ممکنه کړه چې د فرم ویئر په توګه د SeaBIOS کارول. برسېره پردې، د Emscripten په کارولو سره د Qemu بندر کولو لپاره لږترلږه یوه هڅه وه - ما د دې کولو هڅه وکړه ساکټ جوړه، مګر پرمختګ، تر هغه ځایه چې زه پوهیږم، کنګل شوی و.

نو، داسې ښکاري چې دلته سرچینې دي، دلته Emscripten دی - دا واخلئ او تالیف کړئ. مګر داسې کتابتونونه هم شتون لري چې قیمو پورې اړه لري، او هغه کتابتونونه چې په هغه کتابتونونو پورې اړه لري، او داسې نور. libffi، په کوم ګلیب پورې اړه لري. په انټرنیټ کې داسې اوازې وې چې د ایمسکریپټین لپاره د کتابتونونو بندرونو لوی ټولګه کې یو شتون درلود، مګر دا یو څه باور کول سخت وو: لومړی، دا د نوي تالیف کولو لپاره نه و، دویم، دا خورا ټیټه کچه وه. کتابتون یوازې پورته کول، او JS ته تالیف کول. او دا یوازې د مجلس داخلولو مسله نه ده - شاید ، که تاسو دا مسح کړئ ، د ځینې زنګ وهلو کنوانسیونونو لپاره تاسو کولی شئ په سټیک کې اړین دلیلونه رامینځته کړئ او له دوی پرته فنکشن ته زنګ ووهئ. مګر Emscripten یو ستونزمن شی دی: د دې لپاره چې رامینځته شوی کوډ د براوزر JS انجن اصلاح کونکي سره آشنا ښکاري ، ځینې چلونه کارول کیږي. په ځانګړې توګه، تش په نامه بیا ځای پرځای کول - یو کوډ جنریټر د ترلاسه شوي LLVM IR په کارولو سره د ځینې لنډ لیږد لارښوونو سره هڅه کوي د احتمالي ifs، loops، او نور بیا جوړ کړي. ښه، دلیلونه څنګه فنکشن ته لیږدول کیږي؟ په طبیعي توګه، د JS دندو ته د دلیل په توګه، دا د امکان په صورت کې، د سټیک له لارې نه.

په پیل کې یو نظر و چې په ساده ډول د JS سره د libffi لپاره بدیل ولیکئ او معیاري ازموینې پرمخ بوځي، مګر په پای کې زه د دې په اړه مغشوش شوم چې څنګه خپل سرلیک فایلونه جوړ کړم ترڅو دوی د موجوده کوډ سره کار وکړي - زه څه کولی شم، لکه څنګه چې دوی وايي، "ایا دندې دومره پیچلې دي "ایا موږ دومره احمق یو؟" زه باید لیففي بل معمارۍ ته پورټ کړم ، نو د خبرو کولو لپاره - خوشبختانه ، ایمسکریپټین د انلاین اسمبلۍ لپاره دواړه میکرو لري (په جاواسکریپټ کې ، هو - ښه ، هر څه چې جوړښت وي ، نو راټولونکی) ، او په الوتنه کې رامینځته شوي کوډ چلولو وړتیا. په عموم کې ، د یو څه وخت لپاره د پلیټ فارم پورې تړلي لیففي ټوټو سره له مینځه وړلو وروسته ، ما یو څه د تالیف وړ کوډ ترلاسه کړ او په لومړي ازموینه کې چې ما ولیدل. زما د حیرانتیا لپاره، ازموینه بریالۍ وه. زما د هوښیارتیا څخه حیران شوی - هیڅ ټوکه نه ده ، دا د لومړي لانچ څخه کار کاوه - زه لاهم په خپلو سترګو باور نه لرم ، بیا د پایلې کوډ ته د بیاکتنې لپاره لاړم ، ترڅو ارزونه وکړم چې بل چیرې کیندل شي. دلته زه د دوهم ځل لپاره مغز ته لاړم - یوازینی شی چې زما فعالیت یې کړی و ffi_call - دا یو بریالي کال راپور ورکړ. په ځان کې هیڅ غږ نه و. نو ما خپله لومړۍ پلمه غوښتنه واستوله، کوم چې په ازموینه کې یوه تېروتنه سمه کړه چې د اولمپیاډ هر زده کونکي ته روښانه ده - ریښتینې شمیرې باید پرتله نشي a == b او حتی څنګه a - b < EPS - تاسو اړتیا لرئ ماډل هم په یاد ولرئ، که نه نو 0 به د 1/3 سره خورا مساوي وي ... په عموم کې، زه د libffi یو ځانګړي بندر سره راغلم، کوم چې ترټولو ساده ازموینې تیریږي، او کوم چې glib دی. تالیف شوی - ما پریکړه وکړه چې دا به اړین وي، زه به یې وروسته اضافه کړم. مخ ته په کتلو سره، زه به ووایم چې، لکه څنګه چې دا معلومه شوه، تالیف کونکي حتی په وروستي کوډ کې د libffi فعالیت هم نه و شامل کړی.

مګر، لکه څنګه چې ما مخکې وویل، یو څه محدودیتونه شتون لري، او د مختلف غیر تعریف شوي چلند د وړیا کارولو په منځ کې، یو ډیر ناخوښه ځانګړتیا پټه شوې ده - د ډیزاین لخوا جاوا سکریپټ د ګډ حافظې سره ملټي ټریډینګ ملاتړ نه کوي. په اصل کې، دا معمولا حتی یو ښه نظر بلل کیدی شي، مګر د پورټینګ کوډ لپاره نه چې جوړښت یې د C تارونو سره تړلی دی. په عموم کې ، فایرفوکس د شریک کارمندانو ملاتړ کولو تجربه کوي ، او ایمسکریپټین د دوی لپاره د pthread پلي کول لري ، مګر زه نه غواړم پدې تکیه وکړم. ما باید ورو ورو د Qemu کوډ څخه ملټي تھریډینګ له مینځه ویسي - دا دا دی چې دا معلومه کړئ چې تارونه چیرې روان دي ، په دې تار کې د روان لوپ بدن په جلا فنکشن کې حرکت وکړئ ، او دا ډول فنکشنونه د اصلي لوپ څخه یو په یو غږ کړئ.

دوهمه هڅه

په یو څه وخت کې ، دا څرګنده شوه چې ستونزه لاهم شتون لري ، او دا چې د کوډ په شاوخوا کې په ناڅاپي ډول ځړول به هیڅ ښه لامل نشي. پایله: موږ اړتیا لرو چې په یو ډول د کرچونو اضافه کولو پروسه سیستماتیک کړو. له همدې امله ، نسخه 2.4.1 ، کوم چې په هغه وخت کې تازه و ، اخیستل شوی و (نه 2.5.0 ، ځکه چې څوک پوهیږي ، په نوې نسخه کې به داسې کیګونه وي چې لاهم ندي نیول شوي ، او زه خپل کافي بګونه لرم )، او لومړی شی دا و چې دا په خوندي توګه بیا ولیکئ thread-posix.c. ښه ، دا د خوندي په توګه دی: که چیرې یو څوک هڅه وکړي عملیات ترسره کړي چې د بندیدو لامل کیږي ، فنکشن سمدلاسه بلل کیږي abort() - البته، دا په یوځل کې ټولې ستونزې حل نه کړې، مګر لږترلږه دا یو څه په خاموشۍ سره د متضاد معلوماتو ترلاسه کولو څخه ډیر خوندور و.

په عموم کې ، د ایمسکریپټین اختیارونه JS ته د کوډ پور کولو کې خورا ګټور دي -s ASSERTIONS=1 -s SAFE_HEAP=1 - دوی ځینې ډول ډول نه تعریف شوي چلند نیسي، لکه غیر منظم شوي پته ته زنګونه (کوم چې د ټایپ شوي صفونو لپاره د کوډ سره مطابقت نلري لکه HEAP32[addr >> 2] = 1) یا د غلط شمیر دلیلونو سره د فنکشن زنګ وهل.

په هرصورت، د سمون خطا یوه جلا مسله ده. لکه څنګه چې ما مخکې وویل، Qemu د کوډ تولید TCI (کوچني کوډ ژباړونکي) لپاره د "خراب" تشریحي پس منظر لري، او په نوي جوړښت کې د Qemu د جوړولو او چلولو لپاره، که تاسو خوشحاله یاست، د C کمپیلر کافی دی. کلیدي کلمې "که تاسو نېکمرغه یاست". زه بدبخت وم، او دا معلومه شوه چې TCI د خپل بایټ کوډ پارس کولو پر مهال غیر منظم لاسرسی کاروي. دا په هر ډول ARM او نورو جوړښتونو کې چې د لازمي کچې لاسرسي سره ، Qemu تالیف کوي ځکه چې دوی یو نورمال TCG بیکینډ لري چې اصلي کوډ رامینځته کوي ، مګر ایا TCI به په دوی کار وکړي بله پوښتنه ده. په هرصورت، لکه څنګه چې دا معلومه شوه، د TCI اسناد په واضح ډول ورته ورته اشاره وکړه. د پایلې په توګه، د غیر متناسب لوستلو لپاره فنکشن زنګونه په کوډ کې اضافه شوي، کوم چې د کیمو په بله برخه کې موندل شوي.

د هټیو ویجاړول

د پایلې په توګه ، TCI ته غیر منظم لاسرسی سم شو ، یو اصلي لوپ رامینځته شو چې په پایله کې یې پروسیسر ، RCU او ځینې نور کوچني شیان ویل کیږي. او له همدې امله زه Qemu د اختیار سره پیلوم -d exec,in_asm,out_asm، د دې معنی دا ده چې تاسو اړتیا لرئ ووایاست چې د کوډ کوم بلاکونه اجرا کیږي ، او د خپرونې په وخت کې د دې لیکلو لپاره چې د میلمه کوډ څه و ، کوم کوربه کوډ شو (پدې حالت کې ، بایټ کوډ). دا پیل کیږي، د ژباړې ډیری بلاکونه اجرا کوي، د ډیبګ کولو پیغام لیکي چې ما پریښودل چې RCU به اوس پیل شي او ... حادثې abort() د فنکشن دننه free(). د فنکشن سره په ټکر کې free() موږ وکولی شو وموندلو چې د هپ بلاک په سر کې ، کوم چې د تخصیص شوي حافظې دمخه په اتو بایټونو کې پروت دی ، د بلاک اندازې یا ورته څه پرځای ، کثافات شتون درلود.

د ټوټو ویجاړول - څومره ښکلی ... په داسې حالت کې، یو ګټور درملنه شتون لري - (که ممکنه وي) د ورته سرچینو څخه، یو اصلي بائنری راټول کړئ او د والګرینډ لاندې یې چل کړئ. یو څه وخت وروسته، بائنری چمتو شو. زه دا د ورته اختیارونو سره پیلوم - دا حتی د پیل کولو پرمهال سقوط کوي ، مخکې لدې چې واقعیا اجرا ته ورسیږي. دا ناخوښه ده، البته - په ښکاره ډول، سرچینې بالکل ورته نه وې، کوم چې د حیرانتیا خبره نه ده، ځکه چې ترتیب یو څه مختلف انتخابونه موندلي، مګر زه ویلګریند لرم - لومړی به زه دا بګ حل کړم، او بیا، که زه خوشحاله یم. ، اصلي به ښکاره شي. زه د والګرینډ لاندې ورته شی پرمخ وړم ... Y-y-y، y-y-y، uh-uh، دا پیل شو، په نورمال ډول د پیل کولو څخه تیر شو او د غلط حافظې لاسرسي په اړه د یو خبرداری پرته د اصلي بګ څخه تیر شو، نه د زوال په اړه یادونه. ژوند، لکه څنګه چې دوی وايي، ما د دې لپاره چمتو نه کړ - د غورځیدو برنامه د والګرینډ لاندې پیل کیدو سره ټکر کوي. هغه څه وو چې یو راز دی. زما فرضیه دا ده چې یوځل د اوسني لارښوونې په شاوخوا کې د پیل کولو پرمهال د حادثې وروسته ، gdb کار وښود memset-a د یو باوري پوائنټر سره چې دواړه یې کاروي mmx، یا xmm راجستر کوي، بیا شاید دا یو ډول سمون خطا وي، که څه هم دا باور کول لاهم سخت دي.

ښه، ویلګرینډ دلته مرسته نه کوي. او دلته خورا ناوړه شی پیل شو - هرڅه داسې ښکاري چې حتی پیل کیږي ، مګر د یوې پیښې له امله چې ممکن په ملیونونو لارښوونې دمخه پیښ شوي وي په بشپړ ډول د نامعلومو دلایلو لپاره حادثې. د اوږدې مودې لپاره، دا هم روښانه نه وه چې څنګه ورسره مخ شي. په نهایت کې ، زه لاهم باید ناست وم او ډیبګ وکړم. د هغه څه چاپ کول چې سرلیک یې له سره بیا لیکل شوی و وښودله چې دا د شمیرې په څیر نه ښکاري، بلکه یو ډول بائنری ډاټا. او، ګورئ او وګورئ، دا بائنری تار په BIOS فایل کې وموندل شو - دا دی، اوس دا ممکنه وه چې په مناسب باور سره ووایو چې دا د بفر اوور فلو و، او دا حتی روښانه ده چې دا دې بفر ته لیکل شوی. ښه، بیا داسې یو څه - په ایمسکریپټین کې، خوشبختانه، د پتې ځای تصادفي نه دی، په دې کې هیڅ سوري شتون نلري، نو تاسو کولی شئ د وروستي لانچ څخه د پوائنټر په واسطه ډاټا تولیدولو لپاره د کوډ په مینځ کې یو ځای ولیکئ، ډاټا ته وګورئ، پوائنټر ته وګورئ، او که دا نه وي بدل شوی، د فکر لپاره خواړه ترلاسه کړئ. ریښتیا، دا د هر بدلون وروسته لینک کولو لپاره څو دقیقې وخت نیسي، مګر تاسو څه کولی شئ؟ د پایلې په توګه، یو ځانګړی کرښه وموندل شوه چې BIOS یې د لنډمهاله بفر څخه د میلمنو حافظې ته کاپي کړې - او په حقیقت کې، په بفر کې کافي ځای نه و. د دې عجیب بفر پتې سرچینې موندل د فعالیت پایله وه qemu_anon_ram_alloc په دوتنه کې oslib-posix.c - منطق دلته دا و: ځینې وختونه دا ګټور وي چې پته د 2 MB اندازې لوی پا pageې ته تنظیم کړئ ، د دې لپاره به موږ غوښتنه وکړو mmap لومړی یو څه نور، او بیا به موږ د مرستې سره اضافي بیرته راستانه کړو munmap. او که دا ډول سمون ته اړتیا نه وي، نو موږ به د 2 MB پرځای پایله په ګوته کړو getpagesize() - mmap دا به بیا هم یو ترتیب شوی پته ورکړي ... نو په Emscripten کې mmap یوازې زنګ وهي malloc، مګر البته دا په پاڼه کې سمون نه کوي. په عموم کې ، یوه بګ چې ما د څو میاشتو لپاره مایوسه کړی و د بدلون له لارې سم شو двух کرښې

د زنګ وهلو دندو ځانګړتیاوې

او اوس پروسیسر یو څه شمیرل کیږي ، کیمو کریش نه کوي ، مګر سکرین نه چالانیږي ، او پروسیسر په چټکۍ سره لوپ ته ځي ، د محصول لخوا قضاوت کوي -d exec,in_asm,out_asm. یوه فرضیه راپورته شوه: د ټایمر مداخلې (یا په عموم کې، ټول مداخلې) نه راځي. او په حقیقت کې ، که تاسو د اصلي مجلس څخه مداخلې خلاصې کړئ ، کوم چې د یو دلیل لپاره کار کړی ، تاسو ورته عکس ترلاسه کوئ. مګر دا په بشپړ ډول ځواب نه و: د پورتني اختیار سره د صادر شوي نښو پرتله کول وښودله چې د اعدام طریقې خورا دمخه توپیر لري. دلته باید وویل شي چې د هغه څه پرتله کول چې د لانچر په کارولو سره ثبت شوي emrun د اصلي مجلس د محصول سره د ډیبګ کولو محصول په بشپړ ډول میخانیکي پروسه نه ده. زه دقیقا نه پوهیږم چې په براوزر کې روان برنامه څنګه سره وصل کیږي emrun، مګر په محصول کې ځینې لینونه بیا تنظیم شوي دي ، نو په توپیر کې توپیر لاهم د دې دلیل ندی چې ګومان وکړي چې ټراجکټوریز مختلف شوي دي. په عموم کې، دا روښانه شوه چې د لارښوونو سره سم ljmpl مختلف ادرسونو ته لیږد شتون لري ، او رامینځته شوی بایټ کوډ اساسا توپیر لري: یو کې د مرستندویه فنکشن غږولو لپاره لارښوونې شتون لري ، بل یې نه کوي. د لارښوونو د ګوګل کولو او د کوډ مطالعې وروسته چې دا لارښوونې ژباړي، دا روښانه شوه چې لومړی، سمدستي مخکې په راجستر کې cr0 یو ریکارډ جوړ شوی و - د یو مرستندویه په کارولو سره - کوم چې پروسیسر خوندي حالت ته بدل کړ، او دویم دا چې د js نسخه هیڅکله خوندي حالت ته نه وه بدله شوې. مګر حقیقت دا دی چې د Emscripten بله ځانګړتیا د کوډ زغملو لپاره د هغې زړه نازړه ده لکه د لارښوونو پلي کول call په TCI کې، کوم چې کوم فنکشن پوائنټر په ډول کې پایلې لري long long f(int arg0, .. int arg9) - افعال باید د صحیح شمیر دلیلونو سره وبلل شي. که چیرې دا قاعده سرغړونه شوې وي ، د ډیبګ کولو تنظیماتو پورې اړه لري ، برنامه به یا خراب شي (کوم چې ښه دی) یا په بشپړ ډول غلط فعالیت ته زنګ ووهي (کوم چې د ډیبګ کولو لپاره غمجن وي). دریم اختیار هم شتون لري - د ریپرونو نسل فعال کړئ چې دلیلونه اضافه / لرې کوي ، مګر په مجموع کې دا ریپرونه ډیر ځای نیسي ، سره له دې چې په حقیقت کې زه یوازې له سلو څخه لږ څه ډیر ریپرونو ته اړتیا لرم. دا یوازې خورا غمجن دی ، مګر یو ډیر جدي ستونزه رامینځته شوه: د ریپر افعالاتو رامینځته شوي کوډ کې ، دلیلونه بدل شوي او بدل شوي ، مګر ځینې وختونه د رامینځته شوي دلیلونو سره فنکشن نه ویل کیږي - ښه ، لکه څنګه چې زما libffi تطبیق. دا، ځینې مرستندویان په ساده ډول اعدام شوي ندي.

خوشبختانه ، کیمو د سرلیک فایل په څیر د مرستې کونکو ماشین لوستلو وړ لیستونه لري

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 بلاک I/O کوډ په کورټینونو کې لیکل شوی. ایمسکریپټین خپل خورا پیچلي پلي کول لري ، مګر دا لاهم د Qemu کوډ کې ملاتړ ته اړتیا لري ، او تاسو اوس پروسیسر ډیبګ کولی شئ: Qemu د اختیارونو ملاتړ کوي -kernel, -initrd, -append، د کوم سره چې تاسو کولی شئ لینکس بوټ کړئ یا د مثال په توګه ، memtest86+ پرته د بلاک وسیلو کارولو څخه. مګر دلته ستونزه ده: په اصلي مجلس کې یو څوک کولی شي د اختیار سره کنسول ته د لینکس کرنل محصول وګوري -nographic، او د براوزر څخه ترمینل ته هیڅ محصول نه دی راغلی چیرې چې دا پیل شوی و emrun، نه دی راغلی. دا، دا روښانه نده: پروسیسر کار نه کوي یا د ګرافیک محصول کار نه کوي. او بیا ما ته دا پیښ شو چې لږ انتظار وکړم. دا معلومه شوه چې "پروسیسر خوب نه کوي، مګر په ساده ډول په ورو ورو سترګې پټوي،" او شاوخوا پنځه دقیقې وروسته کرنل د پیغامونو یوه ډله کنسول ته وغورځوله او ځړولو ته یې دوام ورکړ. دا څرګنده شوه چې پروسیسر، په عموم کې، کار کوي، او موږ اړتیا لرو چې د SDL2 سره کار کولو لپاره کوډ وګورو. له بده مرغه، زه نه پوهیږم چې دا کتابتون څنګه وکاروم، نو په ځینو ځایونو کې ما باید په تصادفي توګه عمل وکړ. په یو وخت کې ، کرښه موازي 0 په نیلي پس منظر کې په سکرین کې راښکاره شوه ، کوم چې ځینې فکرونه وړاندیز کوي. په پاى کې، دا معلومه شوه چې ستونزه دا وه چې Qemu په يوه فزيکي کړکۍ کې څو مجازي کړکۍ پرانيزي، چې په منځ کې تاسو د Ctrl-Alt-n په کارولو سره بدلولی شئ: دا په اصلي جوړښت کې کار کوي، مګر په Emscripten کې نه. د اختیارونو په کارولو سره د غیر ضروري وینډوز څخه د خلاصون وروسته -monitor none -parallel none -serial none او په هر چوکاټ کې د ټول سکرین په زور سره د بیارغولو لارښوونې، هرڅه ناڅاپه کار وکړ.

کورټینز

نو ، په براوزر کې تقلید کار کوي ، مګر تاسو نشئ کولی پدې کې کوم په زړه پوري واحد فلاپي چل کړئ ، ځکه چې هیڅ بلاک I/O شتون نلري - تاسو اړتیا لرئ د کورټینونو ملاتړ پلي کړئ. Qemu لا دمخه ډیری کورټین بیکینډونه لري ، مګر د جاواسکریپټ طبیعت او د ایمسکریپټین کوډ جنریټر له امله ، تاسو نشئ کولی یوازې د جال سټیکس پیل کړئ. داسې ښکاري چې "هر څه تیر شوي، پلستر لیرې کیږي،" مګر د Emscripten پراختیا کونکو دمخه هرڅه په پام کې نیولي دي. دا خورا مسخره پلي کیږي: راځئ چې د دې مشکوک په څیر فنکشن کال وکړو emscripten_sleep او څو نور د Asyncify میکانیزم په کارولو سره ، په بیله بیا د پوائنټر زنګونه او هر فنکشن ته زنګ وهي چیرې چې د تیرو دوه قضیو څخه یوه ممکن د سټیک لاندې نوره هم پیښ شي. او اوس، د هر شکمن زنګ څخه مخکې، موږ به د async شرایط وټاکو، او د زنګ څخه سمدلاسه وروسته، موږ به وګورو چې آیا یو غیر متناسب کال واقع شوی، او که دا وي، موږ به په دې async شرایطو کې ټول محلي تغیرات خوندي کړو، دا په ګوته کوي چې کوم فعالیت د کنټرول لیږدولو لپاره کله چې موږ اجرا کولو ته دوام ورکولو ته اړتیا لرو، او اوسنی فعالیت پریږدو. دا هغه ځای دی چې د اغیزې مطالعې لپاره فرصت شتون لري ضایع کول - د غیر متناسب زنګ څخه بیرته راستنیدو وروسته د کوډ اجرا کولو ته دوام ورکولو اړتیاو لپاره ، کمپیلر د شکمن زنګ وروسته پیل شوي فنکشن "سټبس" رامینځته کوي - لکه: که چیرې n شکمن تلیفونونه شتون ولري ، نو فنکشن به په n/2 ځای کې پراخه شي. وختونه - دا لاهم دی، که نه په یاد ولرئ چې د هر احتمالي غیر متقابل زنګ وروسته، تاسو اړتیا لرئ چې اصلي فعالیت ته ځینې محلي متغیرونه خوندي کړئ. په تعقیب ، ما حتی په Python کې یو ساده سکریپټ ولیکه ، کوم چې په ځانګړي ډول د ډیر کارول شوي دندو د ورکړل شوي سیټ پراساس چې ګمان کیږي "د ځان له لارې غیر مطابقت ته اجازه نه ورکوي" (دا د سټیک ترویج او هرڅه چې ما یوازې تشریح کړي ندي. په دوی کې کار کوي) د پوائنټرونو له لارې زنګونو ته اشاره کوي په کوم کې چې فنکشنونه باید د کمپیلر لخوا له پامه غورځول شي ترڅو دا افعال غیر متزلزل نه وګڼل شي. او بیا د 60 MB لاندې JS فایلونه په څرګنده توګه خورا ډیر دي - راځئ چې لږ تر لږه 30 ووایو. که څه هم، یوځل چې ما د مجلس سکریپټ ترتیب کړی و، او په ناڅاپي توګه د لینکر اختیارونه وغورځول شول، چې په منځ کې یې و. -O3. زه تولید شوی کوډ چلوم ، او کرومیم حافظه خوري او کریش کوي. ما بیا په ناڅاپي ډول هغه څه ته وکتل چې هغه د ډاونلوډ کولو هڅه کوي ... ښه ، زه څه ویلای شم ، زه به هم منجمد شوی وای که له ما څخه د 500+ MB جاوا سکریپټ په فکري توګه مطالعه او اصلاح کولو غوښتنه شوې وای.

له بده مرغه، د Asyncify ملاتړ کتابتون کوډ کې چکونه په بشپړه توګه دوستانه نه وو longjmp-s چې د مجازی پروسیسر کوډ کې کارول کیږي، مګر د یوې کوچنۍ پیچ وروسته چې دا چکونه غیر فعالوي او په زور سره شرایط بیرته راولي لکه څنګه چې هرڅه سم وو، کوډ کار وکړ. او بیا یو عجیب شی پیل شو: ځینې وختونه د همغږي کولو کوډ کې چیکونه پیل شوي - ورته ورته چې کوډ خرابوي که چیرې د اجرا کولو منطق سره سم ، دا باید بند شي - یو چا هڅه کړې چې دمخه نیول شوی میټیکس ونیسي. خوشبختانه ، دا په سیریل شوي کوډ کې منطقي ستونزه نه وه - ما په ساده ډول د ایمسکریپټین لخوا چمتو شوي معیاري اصلي لوپ فعالیت کارولی و ، مګر ځینې وختونه غیر متناسب کال به سټیک په بشپړ ډول خلاص کړي ، او پدې وخت کې به ناکام شي. setTimeout د اصلي لوپ څخه - پدې توګه، کوډ د پخوانی تکرار پریښودلو پرته د اصلي لوپ تکرار ته ننوتل. په لامحدود لوپ باندې بیا لیکل او emscripten_sleep، او د mutexes سره ستونزې بندې شوې. کوډ حتی ډیر منطقی شوی - په حقیقت کې، زه داسې کوډ نلرم چې راتلونکی حرکت چوکاټ چمتو کړي - پروسیسر یوازې یو څه محاسبه کوي او سکرین په وخت سره تازه کیږي. په هرصورت، ستونزې دلته نه ودریدلې: ځینې وختونه د Qemu اعدام به پرته له کوم استثنا یا غلطی څخه په خاموشۍ سره پای ته ورسیږي. په هغه شیبه کې ما دا پریښوده، مګر، مخکې په لټه کې، زه به ووایم چې ستونزه دا وه: د کورټین کوډ، په حقیقت کې، نه کاروي setTimeout (یا لږترلږه نه څومره چې تاسو فکر کوئ): فعالیت emscripten_yield په ساده ډول د غیر متناسب کال بیرغ تنظیموي. ټوله خبره دا ده emscripten_coroutine_next دا یو غیر متناسب فعالیت نه دی: په داخلي توګه دا بیرغ چیک کوي، بیا یې تنظیموي او کنټرول لیږدوي چیرې چې ورته اړتیا وي. دا دی، د سټیک وده هلته پای ته رسیږي. ستونزه دا وه چې د کارولو وروسته - وړیا ، کوم چې څرګند شو کله چې د کورټین پول غیر فعال شوی و د دې حقیقت له امله چې ما د موجوده کورټین بیکینډ څخه د کوډ مهم کرښه کاپي نه کړه ، فنکشن qemu_in_coroutine ریښتیا بیرته راستانه شوی کله چې په حقیقت کې دا باید غلط راستون شوی وای. دا د تلیفون لامل شو emscripten_yield، چې پورته یې په سټیک کې هیڅوک نه و emscripten_coroutine_next, ډډ خورا لوړ ته ښکاره شو، مګر نه setTimeoutلکه څنګه چې ما مخکې وویل، نندارې ته نه و.

د جاواسکریپټ کوډ تولید

او دلته، په حقیقت کې، ژمنه شوې ده "کیما شوې غوښه بیرته راګرځوي." واقعیآ نه. البته، که موږ په براوزر کې Qemu چلوو، او په دې کې Node.js، نو په طبیعي توګه، په Qemu کې د کوډ تولید وروسته به موږ په بشپړ ډول غلط جاوا سکریپټ ترلاسه کړو. مګر بیا هم، یو ډول برعکس بدلون.

لومړی، د Qemu د کار کولو څرنګوالي په اړه لږ څه. مهرباني وکړئ ما سمدلاسه بښنه وکړئ: زه مسلکي Qemu جوړونکی نه یم او زما پایلې ممکن په ځینو ځایونو کې غلطې وي. لکه څنګه چې دوی وايي، "د زده کونکي نظر باید د ښوونکي نظر، د پیانو محوریت او عام احساس سره سمون ونه لري." Qemu یو ټاکلی شمیر ملاتړ شوي میلمانه جوړښتونه لري او د هر یو لپاره یو لارښود شتون لري لکه target-i386. کله چې جوړول، تاسو کولی شئ د څو میلمنو جوړښتونو لپاره مالتړ مشخص کړئ، مګر پایله به یوازې څو بائنری وي. کوډ د میلمنو جوړښت ملاتړ کوي، په بدل کې، ځینې داخلي Qemu عملیات تولیدوي، کوم چې TCG (کوچني کوډ جنریټر) دمخه د کوربه معمارۍ لپاره د ماشین کوډ بدلوي. لکه څنګه چې د tcg لارښود کې موقعیت لرونکي ریډم فایل کې ویل شوي ، دا په اصل کې د منظم C کمپیلر برخه وه ، کوم چې وروسته د JIT لپاره تطبیق شوی و. له همدې امله، د مثال په توګه، د دې سند په شرایطو کې د هدف معمارۍ نور د میلمنو معمارۍ نه ده، مګر د کوربه معمارۍ. په ځینو وختونو کې، بله برخه ښکاره شوه - د کوچني کوډ ژباړونکي (TCI)، کوم چې باید د ځانګړي کوربه جوړښت لپاره د کوډ جنراتور په نشتوالي کې کوډ (تقریبا ورته داخلي عملیات) اجرا کړي. په حقیقت کې، لکه څنګه چې د دې اسنادو کې راغلي، دا ژباړونکی ممکن تل د JIT کوډ جنریټر په توګه کار نه کوي، نه یوازې د کمیت له پلوه د سرعت له مخې، بلکې په کیفیت کې هم. که څه هم زه ډاډه نه یم چې د هغه توضیحات په بشپړ ډول اړوند دي.

په لومړي سر کې ما هڅه وکړه چې د بشپړ TCG بیکینډ جوړ کړم، مګر په چټکۍ سره د سرچینې کوډ کې ګډوډ شو او د بایټکوډ لارښوونو بشپړ وضاحت نه و، نو ما پریکړه وکړه چې د TCI ژباړونکي وتړم. دا څو ګټې ورکړي:

  • کله چې د کوډ جنریټر پلي کول ، تاسو کولی شئ د لارښوونو توضیحاتو ته ونه ګورئ ، مګر د ژباړونکي کوډ ته
  • تاسو کولی شئ د هرې ژباړې بلاک لپاره چې ورسره مخ شوي فنکشنونه رامینځته کړئ ، مګر د مثال په توګه ، یوازې د سلمې اجرا کولو وروسته
  • که چیرې تولید شوی کوډ بدل شي (او دا ممکنه بریښي ، د نومونو سره د دندو په واسطه قضاوت کول چې د کلمې پیچ لري) ، زه به د تولید شوي JS کوډ باطلولو ته اړتیا ولرم ، مګر لږترلږه زه به د دې څخه د بیا رامینځته کولو لپاره یو څه ولرم.

د دریم ټکي په اړه ، زه ډاډه نه یم چې د لومړي ځل لپاره کوډ اجرا کیدو وروسته پیچ کول ممکن دي ، مګر لومړی دوه ټکي کافي دي.

په پیل کې، کوډ د اصلي بایټکوډ لارښوونې په پته کې د لوی سویچ په بڼه رامینځته شوی و، مګر بیا، د ایمسکریپټین په اړه د مقالې په یادولو سره، د تولید شوي JS اصلاح کول او بیرته راګرځیدل، ما پریکړه وکړه چې نور انساني کوډ تولید کړم، په ځانګړې توګه د تجربې له مخې. معلومه شوه چې د ژباړې بلاک ته د ننوتلو یوازینۍ نقطه د هغې پیل دی. د ترسره کیدو څخه ډیر ژر وویل شو ، یو څه وروسته موږ د کوډ جنریټر درلود چې د ifs سره کوډ رامینځته کوي (که څه هم پرته له لوپونو). مګر بدبختانه ، دا غورځیدلی ، دا پیغام ورکوي چې لارښوونې د یو څه غلط اوږدوالي څخه وې. سربیره پردې، د دې تکرار په کچه وروستۍ لارښوونه وه brcond. سمه ده، زه به د تکراري زنګ څخه مخکې او وروسته د دې لارښوونې نسل ته یو ورته چک اضافه کړم او ... له دوی څخه یو هم نه و اعدام شوی، مګر د اصرار سویچ وروسته دوی لاهم ناکام شوي. په نهایت کې ، د رامینځته شوي کوډ مطالعې وروسته ، ما پوهیده چې د سویچ وروسته ، اوسني لارښوونې ته اشاره له سټیک څخه بیا پورته کیږي او شاید د تولید شوي جاواسکریپټ کوډ لخوا لیکل کیږي. او نو دا معلومه شوه. له یو میګابایټ څخه لسو ته د بفر زیاتوالی د هیڅ شی لامل نه شو، او دا څرګنده شوه چې د کوډ جنریټر په حلقو کې روان دی. موږ باید وګورو چې موږ د اوسني نري رنځ له حدودو څخه بهر نه وو تللي، او که مو وکړل، نو د راتلونکي نري رنځ پته د منفي نښه سره خپره کړئ ترڅو موږ اعدام ته دوام ورکړو. سربیره پردې ، دا ستونزه حل کوي "کوم تولید شوي افعال باید باطل شي که چیرې د بایټ کوډ دا ټوټه بدله شوې وي؟" - یوازې هغه فعالیت چې د دې ژباړې بلاک سره مطابقت لري باید باطل شي. په هرصورت ، که څه هم ما په کرومیم کې هرڅه ډیبګ کړل (ځکه چې زه فایرفاکس کاروم او زما لپاره د تجربو لپاره د جلا براوزر کارول اسانه دي) ، فایرفوکس ما سره د 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 د ځینو نورو عصري نسخو په سر کې د نورمال اټومي ژمنې صادرولو ارزښت لري. په ورته وخت کې، په ګیتا کې د بلاګ په بڼه کې یو تار شتون لري: د هرې "سطحې" لپاره چې لږترلږه یو څه تیر شوي، په روسیه کې مفصل تبصره اضافه شوې. په حقیقت کې، دا مقاله تر ډیره حده د پایلې بیا بیانول دي git log.

تاسو کولی شئ دا ټول هڅه وکړئ دلته (د ترافیکو څخه ځان وساتئ).

هغه څه چې دمخه کار کوي:

  • x86 مجازی پروسیسر چلول
  • د ماشین کوډ څخه جاواسکریپټ ته د JIT کوډ جنریټر کاري پروټوټایپ شتون لري
  • د نورو 32-bit میلمنو جوړښتونو راټولولو لپاره یوه ټیمپلیټ شتون لري: همدا اوس تاسو کولی شئ د بارولو په مرحله کې په براوزر کې د MIPS جوړښت منجمد کولو لپاره لینکس تعریف کړئ

تاسو نور څه کولی شئ

  • ایمولیشن ګړندی کړئ. حتی په JIT حالت کې داسې بریښي چې د مجازی x86 په پرتله ورو پرمخ ځي (مګر په احتمالي توګه د ډیری تقلید شوي هارډویر او معمارۍ سره بشپړ Qemu شتون لري)
  • د نورمال انٹرفیس رامینځته کولو لپاره - په ریښتیا سره ، زه یو ښه ویب جوړونکی نه یم ، نو د اوس لپاره ما د معیاري ایمسکریپین شیل تر هغه ښه جوړ کړی چې زه یې کولی شم
  • هڅه وکړئ د Qemu نور پیچلي فعالیتونه پیل کړئ - شبکه کول، د VM مهاجرت، او نور.
  • UPD: تاسو به اړتیا ولرئ خپل یو څو پرمختګونه او بګ راپورونه ایمسکریپټین اپ سټریم ته وسپارئ ، لکه څنګه چې د کیمو پخوانیو پورټرانو او نورو پروژو کړی و. له دوی څخه مننه چې زما د دندې د یوې برخې په توګه په ایمسکریپټین کې د دوی ونډې په ښکاره ډول کارولو توان درلود.

سرچینه: www.habr.com

Add a comment