QEMU.js: ఇప్పుడు తీవ్రమైన మరియు WASMతో

ఒకప్పుడు నేను సరదాగా నిర్ణయించుకున్నాను ప్రక్రియ యొక్క రివర్సిబిలిటీని నిరూపించండి మరియు మెషిన్ కోడ్ నుండి జావాస్క్రిప్ట్‌ను (మరింత ఖచ్చితంగా, Asm.js) ఎలా రూపొందించాలో తెలుసుకోండి. QEMU ప్రయోగం కోసం ఎంపిక చేయబడింది మరియు కొంత సమయం తరువాత Habr పై ఒక కథనం వ్రాయబడింది. వ్యాఖ్యలలో నేను ప్రాజెక్ట్‌ను వెబ్‌అసెంబ్లీలో రీమేక్ చేయమని సలహా ఇచ్చాను మరియు నేనే నిష్క్రమించాను దాదాపు పూర్తి నేను ఏదో ఒకవిధంగా ప్రాజెక్ట్ కోరుకోలేదు ... పని జరుగుతోంది, కానీ చాలా నెమ్మదిగా, మరియు ఇప్పుడు, ఇటీవల ఆ కథనంలో కనిపించింది వ్యాఖ్యను "కాబట్టి ఇదంతా ఎలా ముగిసింది?" అనే అంశంపై నా వివరణాత్మక సమాధానానికి ప్రతిస్పందనగా, “ఇది కథనంలా ఉంది” అని విన్నాను. సరే, మీకు వీలైతే, ఒక వ్యాసం ఉంటుంది. బహుశా ఎవరైనా ఉపయోగకరంగా ఉండవచ్చు. దీని నుండి QEMU కోడ్ జనరేషన్ బ్యాకెండ్‌ల రూపకల్పన గురించి, అలాగే వెబ్ అప్లికేషన్ కోసం జస్ట్-ఇన్-టైమ్ కంపైలర్‌ను ఎలా వ్రాయాలి అనే దాని గురించి రీడర్ కొన్ని వాస్తవాలను నేర్చుకుంటారు.

పనులు

QEMUని జావాస్క్రిప్ట్‌కి ఎలా పోర్ట్ చేయాలో "ఏదో ఒకవిధంగా" నేను ఇప్పటికే నేర్చుకున్నాను కాబట్టి, ఈసారి దానిని తెలివిగా చేయాలని మరియు పాత తప్పులను పునరావృతం చేయకూడదని నిర్ణయించుకున్నాను.

లోపం నంబర్ వన్: పాయింట్ విడుదల నుండి శాఖ

అప్‌స్ట్రీమ్ వెర్షన్ 2.4.1 నుండి నా వెర్షన్‌ను ఫోర్క్ చేయడం నా మొదటి తప్పు. అప్పుడు నాకు ఇది మంచి ఆలోచనగా అనిపించింది: పాయింట్ విడుదల ఉంటే, అది బహుశా సాధారణ 2.4 కంటే స్థిరంగా ఉంటుంది మరియు ఇంకా ఎక్కువ శాఖ master. మరియు నేను నా స్వంత బగ్‌ల యొక్క సరసమైన మొత్తాన్ని జోడించాలని ప్లాన్ చేసినందున, నాకు మరెవరూ అవసరం లేదు. బహుశా అది ఎలా మారింది. కానీ ఇక్కడ విషయం ఏమిటంటే: QEMU నిశ్చలంగా లేదు మరియు ఏదో ఒక సమయంలో వారు రూపొందించిన కోడ్‌ని 10 శాతం వరకు ఆప్టిమైజేషన్‌ని కూడా ప్రకటించారు. ఇక్కడ మనం డైగ్రెషన్ చేయవలసి ఉంది: QEMU.js యొక్క సింగిల్-థ్రెడ్ స్వభావం మరియు అసలు QEMU బహుళ-థ్రెడింగ్ లేకపోవడాన్ని సూచించదు (అంటే, అనేక సంబంధం లేని కోడ్ పాత్‌లను ఏకకాలంలో ఆపరేట్ చేయగల సామర్థ్యం మరియు కేవలం "అన్ని కెర్నల్‌లను ఉపయోగించండి") దీనికి కీలకం, థ్రెడ్‌ల యొక్క ప్రధాన విధులు నేను బయటి నుండి కాల్ చేయగలగడానికి "దానిని తిప్పికొట్టాలి". ఇది విలీనం సమయంలో కొన్ని సహజ సమస్యలను సృష్టించింది. అయితే ఆ శాఖ నుంచి కొన్ని మార్పులు చోటు చేసుకున్నాయన్నది వాస్తవం master, నేను నా కోడ్‌ను విలీనం చేయడానికి ప్రయత్నించాను, పాయింట్ విడుదలలో చెర్రీ ఎంపిక చేయబడింది (అందువలన నా బ్రాంచ్‌లో) కూడా బహుశా సౌలభ్యాన్ని జోడించి ఉండకపోవచ్చు.

సాధారణంగా, ప్రోటోటైప్‌ను విసిరివేయడం, భాగాల కోసం విడదీయడం మరియు క్రొత్త వాటి ఆధారంగా మొదటి నుండి క్రొత్త సంస్కరణను రూపొందించడం ఇంకా అర్ధమే అని నేను నిర్ణయించుకున్నాను. master.

తప్పు సంఖ్య రెండు: TLP పద్దతి

సారాంశంలో, ఇది పొరపాటు కాదు, సాధారణంగా, ఇది “ఎక్కడ మరియు ఎలా తరలించాలి?” మరియు సాధారణంగా “మేము అక్కడికి చేరుకుంటామా?” రెండింటిపై పూర్తి అపార్థం ఉన్న పరిస్థితులలో ప్రాజెక్ట్‌ను రూపొందించే లక్షణం. ఈ పరిస్థితుల్లో వికృతమైన ప్రోగ్రామింగ్ సమర్థించబడిన ఎంపిక, కానీ, సహజంగానే, నేను దానిని అనవసరంగా పునరావృతం చేయాలనుకోలేదు. ఈసారి నేను దీన్ని తెలివిగా చేయాలనుకున్నాను: అటామిక్ కమిట్‌లు, చేతన కోడ్ మార్పులు (మరియు “కంపైల్ చేసే వరకు (హెచ్చరికలతో) యాదృచ్ఛిక అక్షరాలను కలపడం కాదు”, వికీకోట్ ప్రకారం లైనస్ టోర్వాల్డ్స్ ఒకరి గురించి ఒకసారి చెప్పినట్లు) మొదలైనవి.

తప్పు సంఖ్య మూడు: ఫోర్డ్ తెలియకుండా నీటిలోకి ప్రవేశించడం

నేను ఇప్పటికీ దీని నుండి పూర్తిగా బయటపడలేదు, కానీ ఇప్పుడు నేను కనీసం ప్రతిఘటన యొక్క మార్గాన్ని అనుసరించకూడదని నిర్ణయించుకున్నాను మరియు “వయోజనంగా” దీన్ని చేయాలని నిర్ణయించుకున్నాను, అంటే, నా TCG బ్యాకెండ్‌ను మొదటి నుండి వ్రాయండి. తరువాత చెప్పవలసింది, “అవును, ఇది నిదానంగా జరుగుతుంది, కానీ నేను అన్నింటినీ నియంత్రించలేను - TCI ఇలా వ్రాయబడింది...” అంతేకాకుండా, ఇది మొదట్లో స్పష్టమైన పరిష్కారంగా అనిపించింది, ఎందుకంటే నేను బైనరీ కోడ్‌ని రూపొందిస్తాను. వారు చెప్పినట్లు, “ఘెంట్ సేకరించారుу, కానీ అది ఒకటి కాదు”: కోడ్, వాస్తవానికి, బైనరీ, కానీ నియంత్రణను దానికి బదిలీ చేయడం సాధ్యపడదు - సంకలనం కోసం ఇది స్పష్టంగా బ్రౌజర్‌లోకి నెట్టబడాలి, దీని ఫలితంగా JS ప్రపంచం నుండి ఒక నిర్దిష్ట వస్తువు వస్తుంది, ఇది ఇంకా అవసరం ఎక్కడో రక్షింపబడాలి. అయినప్పటికీ, సాధారణ RISC ఆర్కిటెక్చర్‌లలో, నేను అర్థం చేసుకున్నంతవరకు, రీజెనరేటెడ్ కోడ్ కోసం ఇన్‌స్ట్రక్షన్ కాష్‌ని స్పష్టంగా రీసెట్ చేయాల్సిన అవసరం ఒక సాధారణ పరిస్థితి - ఇది మనకు అవసరం కాకపోతే, ఏ సందర్భంలోనైనా, ఇది దగ్గరగా ఉంటుంది. అదనంగా, నా చివరి ప్రయత్నం నుండి, నియంత్రణ అనువాద బ్లాక్ మధ్యలోకి బదిలీ చేయబడలేదని నేను తెలుసుకున్నాను, కాబట్టి మనకు నిజంగా ఏదైనా ఆఫ్‌సెట్ నుండి బైట్‌కోడ్ అన్వయించాల్సిన అవసరం లేదు మరియు మేము దానిని TBలోని ఫంక్షన్ నుండి రూపొందించవచ్చు. .

వాళ్ళు వచ్చి తన్నాడు

నేను జూలైలో తిరిగి కోడ్‌ని తిరిగి వ్రాయడం ప్రారంభించినప్పటికీ, ఒక మ్యాజిక్ కిక్ గుర్తించబడదు: సాధారణంగా GitHub నుండి ఉత్తరాలు సమస్యలు మరియు పుల్ అభ్యర్థనలకు ప్రతిస్పందనల గురించి నోటిఫికేషన్‌లుగా వస్తాయి, కానీ ఇక్కడ, అకస్మాత్తుగా థ్రెడ్‌లో పేర్కొన్నారు ఒక qemu బ్యాకెండ్ వలె Binaryen సందర్భంలో, "అతను అలాంటిదే చేసాడు, బహుశా అతను ఏదైనా చెబుతాడు." మేము Emscripten సంబంధిత లైబ్రరీని ఉపయోగించడం గురించి మాట్లాడుతున్నాము బైనరీన్ WASM JITని సృష్టించడానికి. సరే, మీకు అక్కడ Apache 2.0 లైసెన్స్ ఉందని నేను చెప్పాను మరియు QEMU మొత్తం GPLv2 క్రింద పంపిణీ చేయబడుతుంది మరియు అవి చాలా అనుకూలంగా లేవు. అకస్మాత్తుగా లైసెన్స్ ఉండవచ్చని తేలింది దాన్ని ఎలాగోలా పరిష్కరించండి (నాకు తెలియదు: బహుశా దానిని మార్చవచ్చు, ద్వంద్వ లైసెన్సింగ్ ఉండవచ్చు, మరేదైనా కావచ్చు...). ఇది, వాస్తవానికి, నాకు సంతోషాన్ని కలిగించింది, ఎందుకంటే ఆ సమయానికి నేను ఇప్పటికే దగ్గరగా చూశాను బైనరీ ఫార్మాట్ WebAssembly, మరియు నేను ఏదో ఒకవిధంగా విచారంగా మరియు అర్థం చేసుకోలేకపోయాను. పరివర్తన గ్రాఫ్‌తో ప్రాథమిక బ్లాక్‌లను మ్రింగివేసే లైబ్రరీ కూడా ఉంది, బైట్‌కోడ్‌ను ఉత్పత్తి చేస్తుంది మరియు అవసరమైతే దాన్ని ఇంటర్‌ప్రెటర్‌లోనే అమలు చేస్తుంది.

అప్పుడు మరింత ఉంది ఒక లేఖ QEMU మెయిలింగ్ లిస్ట్‌లో ఉంది, అయితే ఇది "ఎవరికి అవసరం?" అనే ప్రశ్నకు సంబంధించినది. మరియు అది అకస్మాత్తుగా, ఇది అవసరమని తేలింది. కనిష్టంగా, ఇది ఎక్కువ లేదా తక్కువ త్వరగా పని చేస్తే, మీరు క్రింది ఉపయోగ అవకాశాలను కలిపి స్క్రాప్ చేయవచ్చు:

  • ఎటువంటి ఇన్‌స్టాలేషన్ లేకుండానే ఏదో ఒక విద్యాసంబంధాన్ని ప్రారంభించడం
  • iOSలో వర్చువలైజేషన్, ఇక్కడ, పుకార్ల ప్రకారం, ఫ్లైలో కోడ్ ఉత్పత్తి చేసే హక్కు ఉన్న ఏకైక అప్లికేషన్ JS ఇంజిన్ (ఇది నిజమేనా?)
  • మినీ-OS యొక్క ప్రదర్శన - సింగిల్-ఫ్లాపీ, అంతర్నిర్మిత, అన్ని రకాల ఫర్మ్‌వేర్ మొదలైనవి...

బ్రౌజర్ రన్‌టైమ్ ఫీచర్‌లు

నేను ఇప్పటికే చెప్పినట్లుగా, QEMU మల్టీథ్రెడింగ్‌తో ముడిపడి ఉంది, కానీ బ్రౌజర్‌లో అది లేదు. సరే, అంటే, లేదు... మొదట్లో అది ఉనికిలో లేదు, తర్వాత వెబ్‌వర్కర్లు కనిపించారు - నేను అర్థం చేసుకున్నంత వరకు, ఇది మెసేజ్ పాస్ ఆధారంగా మల్టీథ్రెడింగ్ భాగస్వామ్య వేరియబుల్స్ లేకుండా. సహజంగానే, షేర్డ్ మెమరీ మోడల్ ఆధారంగా ఇప్పటికే ఉన్న కోడ్‌ను పోర్ట్ చేసేటప్పుడు ఇది ముఖ్యమైన సమస్యలను సృష్టిస్తుంది. ఆ తర్వాత ప్రజల ఒత్తిడి మేరకే దాన్ని కూడా పేరుతో అమలు చేశారు SharedArrayBuffers. ఇది క్రమంగా పరిచయం చేయబడింది, వారు వివిధ బ్రౌజర్‌లలో దాని లాంచ్‌ని జరుపుకున్నారు, ఆపై వారు కొత్త సంవత్సరాన్ని జరుపుకున్నారు, ఆపై మెల్ట్‌డౌన్... ఆ తర్వాత వారు సమయ కొలత ముతక లేదా ముతక అని నిర్ధారణకు వచ్చారు, కానీ షేర్డ్ మెమరీ సహాయంతో మరియు ఒక థ్రెడ్ కౌంటర్‌ను పెంచుతుంది, ఇది ఒకే విధంగా ఉంటుంది ఇది చాలా ఖచ్చితంగా పని చేస్తుంది. కాబట్టి మేము షేర్డ్ మెమరీతో మల్టీథ్రెడింగ్‌ని నిలిపివేసాము. వారు తర్వాత దాన్ని తిరిగి ఆన్ చేసినట్లు తెలుస్తోంది, కానీ, మొదటి ప్రయోగం నుండి స్పష్టంగా కనిపించినందున, అది లేకుండా జీవితం ఉంది మరియు అలా అయితే, మేము మల్టీథ్రెడింగ్‌పై ఆధారపడకుండా దీన్ని చేయడానికి ప్రయత్నిస్తాము.

రెండవ లక్షణం స్టాక్‌తో తక్కువ-స్థాయి మానిప్యులేషన్‌ల అసంభవం: మీరు కేవలం తీసుకోలేరు, ప్రస్తుత సందర్భాన్ని సేవ్ చేసి కొత్త స్టాక్‌తో కొత్తదానికి మారలేరు. కాల్ స్టాక్ JS వర్చువల్ మెషీన్ ద్వారా నిర్వహించబడుతుంది. మునుపటి ప్రవాహాలను పూర్తిగా మానవీయంగా నిర్వహించాలని మేము ఇంకా నిర్ణయించుకున్నందున సమస్య ఏమిటి? వాస్తవం ఏమిటంటే, QEMUలోని బ్లాక్ I/O కొరౌటిన్‌ల ద్వారా అమలు చేయబడుతుంది మరియు ఇక్కడే తక్కువ-స్థాయి స్టాక్ మానిప్యులేషన్‌లు ఉపయోగపడతాయి. అదృష్టవశాత్తూ, Emscipten ఇప్పటికే అసమకాలిక కార్యకలాపాల కోసం ఒక యంత్రాంగాన్ని కలిగి ఉంది, రెండు కూడా: సమకాలీకరించు и వ్యాఖ్యాత. మొదటిది రూపొందించబడిన జావాస్క్రిప్ట్ కోడ్‌లో ముఖ్యమైన బ్లోట్ ద్వారా పని చేస్తుంది మరియు ఇకపై మద్దతు లేదు. రెండవది ప్రస్తుత "సరైన మార్గం" మరియు స్థానిక వ్యాఖ్యాత కోసం బైట్‌కోడ్ ఉత్పత్తి ద్వారా పని చేస్తుంది. ఇది నెమ్మదిగా పని చేస్తుంది, కానీ ఇది కోడ్‌ను ఉబ్బరించదు. నిజమే, ఈ మెకానిజం కోసం కొరౌటిన్‌ల కోసం మద్దతు స్వతంత్రంగా అందించబడాలి (అసిన్సిఫై కోసం ఇప్పటికే కొరౌటిన్‌లు వ్రాయబడ్డాయి మరియు ఎంటర్‌ప్రెటర్ కోసం దాదాపు అదే API అమలులో ఉంది, మీరు వాటిని కనెక్ట్ చేయాల్సి ఉంటుంది).

ప్రస్తుతానికి, నేను ఇంకా కోడ్‌ను WASMలో కంపైల్ చేసి, ఎంటర్‌ప్రెటర్‌ని ఉపయోగించి అర్థం చేసుకోలేకపోయాను, కాబట్టి బ్లాక్ పరికరాలు ఇంకా పని చేయడం లేదు (తదుపరి సిరీస్‌లో వారు చెప్పినట్లు చూడండి...). అంటే, చివరికి మీరు ఇలాంటి ఫన్నీ లేయర్డ్ థింగ్‌ని పొందాలి:

  • ఇంటర్‌ప్రెటెడ్ బ్లాక్ I/O. సరే, స్థానిక పనితీరుతో NVMeని అనుకరించాలని మీరు నిజంగా ఆశించారా? 🙂
  • స్థిరంగా సంకలనం చేయబడిన ప్రధాన QEMU కోడ్ (అనువాదకుడు, ఇతర అనుకరణ పరికరాలు మొదలైనవి)
  • WASMలోకి డైనమిక్‌గా కంపైల్ చేయబడిన అతిథి కోడ్

QEMU మూలాల యొక్క లక్షణాలు

మీరు బహుశా ఇప్పటికే ఊహించినట్లుగా, అతిథి నిర్మాణాలను అనుకరించే కోడ్ మరియు హోస్ట్ మెషీన్ సూచనలను రూపొందించే కోడ్ QEMUలో వేరు చేయబడ్డాయి. నిజానికి, ఇది కొంచెం ఉపాయం కూడా:

  • అతిథి నిర్మాణాలు ఉన్నాయి
  • ఉంది యాక్సిలరేటర్లు, అంటే, Linuxలో హార్డ్‌వేర్ వర్చువలైజేషన్ కోసం 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కి ప్రత్యామ్నాయం, ఇకపై చెల్లుబాటు అయ్యే JavaScript కోడ్ వలె నటించడం లేదు. దీనికి విరుద్ధంగా, ఇది పూర్తిగా బైనరీ మరియు ఆప్టిమైజ్ చేయబడింది మరియు దానిలో పూర్ణాంకాన్ని వ్రాయడం కూడా చాలా సులభం కాదు: కాంపాక్ట్‌నెస్ కోసం, ఇది ఆకృతిలో నిల్వ చేయబడుతుంది LEB128.

మీరు Asm.js కోసం రీలూపింగ్ అల్గోరిథం గురించి విని ఉండవచ్చు - ఇది “అధిక స్థాయి” ప్రవాహ నియంత్రణ సూచనల పునరుద్ధరణ (అంటే, అయితే, లూప్‌లు మొదలైనవి), దీని కోసం JS ఇంజిన్‌లు రూపొందించబడ్డాయి తక్కువ-స్థాయి LLVM IR, ప్రాసెసర్ ద్వారా అమలు చేయబడిన మెషిన్ కోడ్‌కు దగ్గరగా ఉంటుంది. సహజంగానే, QEMU యొక్క ఇంటర్మీడియట్ ప్రాతినిధ్యం రెండవదానికి దగ్గరగా ఉంటుంది. ఇది ఇక్కడ ఉంది, బైట్‌కోడ్, హింసకు ముగింపు అని అనిపించవచ్చు... ఆపై బ్లాక్‌లు ఉన్నాయి, అయితే-అప్పుడు-ఎక్కువ మరియు లూప్‌లు!..

మరియు ఇది Binaryen ఉపయోగకరంగా ఉండటానికి మరొక కారణం: ఇది సహజంగా WASMలో నిల్వ చేయబడే వాటికి దగ్గరగా ఉన్న అధిక-స్థాయి బ్లాక్‌లను అంగీకరించగలదు. కానీ ఇది ప్రాథమిక బ్లాక్‌లు మరియు వాటి మధ్య పరివర్తనాల గ్రాఫ్ నుండి కోడ్‌ను కూడా ఉత్పత్తి చేయగలదు. సరే, ఇది అనుకూలమైన C/C++ API వెనుక WebAssembly నిల్వ ఆకృతిని దాచిపెడుతుందని నేను ఇప్పటికే చెప్పాను.

TCG (చిన్న కోడ్ జనరేటర్)

టిసిజి నిజానికి ఉంది C కంపైలర్ కోసం బ్యాకెండ్ అప్పుడు, స్పష్టంగా, ఇది GCCతో పోటీని తట్టుకోలేకపోయింది, కానీ చివరికి అది హోస్ట్ ప్లాట్‌ఫారమ్ కోసం కోడ్ జనరేషన్ మెకానిజం వలె QEMUలో తన స్థానాన్ని కనుగొంది. కొన్ని వియుక్త బైట్‌కోడ్‌ను రూపొందించే TCG బ్యాకెండ్ కూడా ఉంది, ఇది వ్యాఖ్యాత ద్వారా వెంటనే అమలు చేయబడుతుంది, కానీ నేను ఈసారి దాన్ని ఉపయోగించకూడదని నిర్ణయించుకున్నాను. అయితే, QEMUలో ఫంక్షన్ ద్వారా ఉత్పత్తి చేయబడిన TBకి పరివర్తనను ప్రారంభించడం ఇప్పటికే సాధ్యమే. 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-ov:

  • టార్గెట్ ఆర్కిటెక్చర్‌లో ఎన్ని రిజిస్టర్‌లు మరియు ఎంత వెడల్పు ఉన్నాయి (మనకు కావలసినన్ని ఉన్నాయి, మనకు కావలసినన్ని ఉన్నాయి - “పూర్తిగా లక్ష్యం” ఆర్కిటెక్చర్‌లో బ్రౌజర్ ద్వారా మరింత సమర్థవంతమైన కోడ్‌గా ఏది రూపొందించబడుతుందనే ప్రశ్న ఎక్కువగా ఉంటుంది ...)
  • హోస్ట్ సూచనల అమరిక: 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 ప్రారంభంలో మెమరీని కేటాయిస్తుంది మరియు అక్కడ ఉత్పత్తి చేయబడిన కోడ్‌ను వ్రాస్తుంది. బఫర్ అయిపోయినప్పుడు, కోడ్ విసిరివేయబడుతుంది మరియు దాని స్థానంలో తదుపరిది వ్రాయడం ప్రారంభమవుతుంది.

కోడ్‌ను అధ్యయనం చేసిన తర్వాత, మ్యాజిక్ నంబర్‌తో కూడిన ట్రిక్ మొదటి పాస్‌లో ప్రారంభించబడని బఫర్‌లో ఏదో తప్పుని తొలగించడం ద్వారా కుప్ప విధ్వంసంలో విఫలం కాకుండా ఉండటానికి నన్ను అనుమతించిందని నేను గ్రహించాను. అయితే నా ఫంక్షన్‌ని తర్వాత దాటవేయడానికి బఫర్‌ను ఎవరు తిరిగి వ్రాస్తారు? ఎమ్‌స్క్రిప్టెన్ డెవలపర్‌లు సలహా ఇచ్చినట్లుగా, నేను సమస్యలో చిక్కుకున్నప్పుడు, నేను ఫలిత కోడ్‌ను స్థానిక అప్లికేషన్‌కి తిరిగి పోర్ట్ చేసాను, దానిపై మొజిల్లా రికార్డ్-రీప్లే సెట్ చేసాను... సాధారణంగా, చివరికి నేను ఒక సాధారణ విషయాన్ని గ్రహించాను: ప్రతి బ్లాక్ కోసం, a 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.

మార్గం ద్వారా, డెమో (ప్రస్తుతం మర్కీ లైసెన్స్‌తో) Firefoxలో మాత్రమే బాగా పని చేస్తుంది. Chrome డెవలపర్లు ఉన్నారు ఏదో ఒకవిధంగా సిద్ధంగా లేదు ఎవరైనా WebAssembly మాడ్యూల్‌ల యొక్క వెయ్యికి పైగా ఉదాహరణలను సృష్టించాలనుకుంటున్నారు, కాబట్టి వారు ప్రతి దాని కోసం ఒక గిగాబైట్ వర్చువల్ అడ్రస్ స్థలాన్ని కేటాయించారు...

ఇప్పటికి ఇంతే. ఎవరైనా ఆసక్తి కలిగి ఉంటే బహుశా మరొక వ్యాసం ఉంటుంది. అవి, కనీసం మిగిలి ఉన్నాయి కేవలం బ్లాక్ పరికరాలను పని చేసేలా చేయండి. వెబ్‌అసెంబ్లీ మాడ్యూల్‌ల సంకలనాన్ని అసమకాలికంగా మార్చడం కూడా సమంజసంగా ఉండవచ్చు, ఎందుకంటే స్థానిక మాడ్యూల్ సిద్ధమయ్యే వరకు ఇవన్నీ చేయగల ఒక వ్యాఖ్యాత ఇప్పటికీ ఉన్నారు.

చివరగా ఒక చిక్కు: మీరు 32-బిట్ ఆర్కిటెక్చర్‌పై బైనరీని కంపైల్ చేసారు, అయితే కోడ్, మెమరీ ఆపరేషన్‌ల ద్వారా, బైనరీన్ నుండి, స్టాక్‌లో ఎక్కడో లేదా 2-బిట్ అడ్రస్ స్పేస్‌లోని ఎగువ 32 GBలో ఎక్కడైనా పెరుగుతుంది. సమస్య ఏమిటంటే, బైనరీన్ దృష్టికోణంలో ఇది చాలా పెద్ద ఫలిత చిరునామాను యాక్సెస్ చేస్తోంది. దీన్ని ఎలా అధిగమించాలి?

అడ్మిన్ మార్గంలో

నేను దీన్ని పరీక్షించడం ముగించలేదు, కానీ నా మొదటి ఆలోచన ఏమిటంటే "నేను 32-బిట్ లైనక్స్‌ని ఇన్‌స్టాల్ చేస్తే ఎలా?" అప్పుడు చిరునామా స్థలం ఎగువ భాగం కెర్నల్ ద్వారా ఆక్రమించబడుతుంది. ఎంత ఆక్రమించబడుతుందనేది మాత్రమే ప్రశ్న: 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));
}

... ఇది వాల్‌గ్రైండ్‌తో అనుకూలంగా లేదనేది నిజం, కానీ, అదృష్టవశాత్తూ, వాల్‌గ్రైండ్ చాలా ప్రభావవంతంగా అందరినీ అక్కడి నుండి బయటకు నెట్టివేస్తుంది :)

బహుశా ఎవరైనా ఈ నా కోడ్ ఎలా పనిచేస్తుందనే దాని గురించి మెరుగైన వివరణ ఇస్తారు...

మూలం: www.habr.com

ఒక వ్యాఖ్యను జోడించండి