Qemu.js พร้อมรองรับ JIT: คุณยังสามารถหมุนสับไปข้างหลังได้

ไม่กี่ปีที่ผ่านมา ฟาบริซ เบลลาร์ด เขียนโดย jslinux เป็นโปรแกรมจำลองพีซีที่เขียนด้วย JavaScript หลังจากนั้นก็มีอย่างน้อยอีก เสมือน x86. แต่เท่าที่ฉันรู้ พวกเขาทั้งหมดเป็นล่าม ในขณะที่ Qemu ซึ่งเขียนก่อนหน้านี้มากโดย Fabrice Bellard คนเดียวกัน และอาจเป็นโปรแกรมจำลองสมัยใหม่ที่เคารพตนเอง ใช้ JIT รวบรวมรหัสแขกลงในรหัสระบบโฮสต์ สำหรับฉันดูเหมือนว่าถึงเวลาแล้วที่จะต้องใช้งานสิ่งที่ตรงกันข้ามกับงานที่เบราว์เซอร์แก้ไข: การคอมไพล์ JIT ของรหัสเครื่องลงใน JavaScript ซึ่งดูเหมือนสมเหตุสมผลที่สุดในการพอร์ต Qemu ดูเหมือนว่าเหตุใด Qemu จึงมีโปรแกรมจำลองที่ง่ายกว่าและใช้งานง่าย - เช่น VirtualBox เดียวกัน - ติดตั้งและใช้งานได้ แต่ Qemu มีคุณสมบัติที่น่าสนใจหลายประการ

  • โอเพ่นซอร์ส
  • ความสามารถในการทำงานโดยไม่ต้องใช้ไดรเวอร์เคอร์เนล
  • ความสามารถในการทำงานในโหมดล่าม
  • รองรับสถาปัตยกรรมโฮสต์และแขกจำนวนมาก

เกี่ยวกับประเด็นที่สาม ตอนนี้ฉันสามารถอธิบายได้ว่าในความเป็นจริงในโหมด TCI ไม่ใช่คำสั่งของเครื่องแขกที่ตีความ แต่เป็นรหัสไบต์ที่ได้รับจากพวกเขา แต่สิ่งนี้ไม่ได้เปลี่ยนสาระสำคัญ - เพื่อที่จะสร้างและเรียกใช้ Qemu บนสถาปัตยกรรมใหม่ หากคุณโชคดี คอมไพเลอร์ AC ก็เพียงพอแล้ว - การเขียนตัวสร้างโค้ดสามารถเลื่อนออกไปได้

และตอนนี้หลังจากสองปีของการแก้ไขซอร์สโค้ด Qemu ในเวลาว่างของฉันต้นแบบที่ใช้งานได้ก็ปรากฏขึ้นซึ่งคุณสามารถเรียกใช้ได้แล้วเช่น Kolibri OS

Emscripten คืออะไร

ปัจจุบันมีคอมไพเลอร์จำนวนมากปรากฏขึ้น ผลลัพธ์สุดท้ายคือ JavaScript บางอย่าง เช่น Type Script เดิมทีตั้งใจให้เป็นวิธีที่ดีที่สุดในการเขียนสำหรับเว็บ ในเวลาเดียวกัน Emscripten เป็นวิธีหนึ่งในการนำโค้ด C หรือ C++ ที่มีอยู่แล้วคอมไพล์เป็นรูปแบบที่เบราว์เซอร์อ่านได้ บน หน้านี้ เราได้รวบรวมพอร์ตของโปรแกรมที่มีชื่อเสียงมากมาย: ที่นี่ตัวอย่างเช่น คุณสามารถดู PyPy ได้ อย่างไรก็ตาม พวกเขาอ้างว่ามี JIT อยู่แล้ว ในความเป็นจริงไม่ใช่ทุกโปรแกรมที่สามารถคอมไพล์และรันในเบราว์เซอร์ได้ง่ายๆ - มีหลายโปรแกรม คุณสมบัติซึ่งคุณต้องทนด้วย ดังที่จารึกในหน้าเดียวกันเขียนว่า “Emscripten สามารถใช้ในการคอมไพล์ได้เกือบทุกชนิด แบบพกพา รหัส C/C++ เป็น JavaScript" กล่าวคือ มีการดำเนินการจำนวนหนึ่งที่เป็นลักษณะการทำงานที่ไม่ได้กำหนดไว้ตามมาตรฐาน แต่โดยปกติจะทำงานบน x86 - ตัวอย่างเช่น การเข้าถึงตัวแปรที่ไม่สอดคล้องกัน ซึ่งโดยทั่วไปแล้วเป็นสิ่งต้องห้ามในสถาปัตยกรรมบางตัว โดยทั่วไป , Qemu เป็นโปรแกรมข้ามแพลตฟอร์ม และ ฉันอยากจะเชื่อ และมันไม่มีพฤติกรรมที่ไม่ได้กำหนดไว้มากมาย - เอาไปคอมไพล์ จากนั้นปรับแต่ง JIT เล็กน้อย - เท่านี้ก็เสร็จแล้ว! กรณี...

ครั้งแรกลอง

โดยทั่วไปแล้ว ฉันไม่ใช่คนแรกที่มีแนวคิดในการย้าย Qemu ไปยัง JavaScript มีคำถามถามในฟอรัม ReactOS ว่าเป็นไปได้โดยใช้ Emscripten หรือไม่ ก่อนหน้านี้ มีข่าวลือว่า Fabrice Bellard ทำสิ่งนี้เป็นการส่วนตัว แต่เรากำลังพูดถึง jslinux ซึ่งเท่าที่ฉันรู้ เป็นเพียงความพยายามที่จะบรรลุประสิทธิภาพที่เพียงพอด้วยตนเองใน JS และถูกเขียนตั้งแต่เริ่มต้น ต่อมามีการเขียน Virtual x86 - มีการโพสต์แหล่งที่มาที่ไม่ซับซ้อนและตามที่ระบุไว้ "ความสมจริง" ที่ยิ่งใหญ่กว่าของการจำลองทำให้สามารถใช้ SeaBIOS เป็นเฟิร์มแวร์ได้ นอกจากนี้ยังมีความพยายามอย่างน้อยหนึ่งครั้งในการย้ายพอร์ต Qemu โดยใช้ Emscripten - ฉันพยายามทำสิ่งนี้ ซ็อกเก็ตคู่แต่เท่าที่ฉันเข้าใจการพัฒนาก็ถูกแช่แข็ง

ดูเหมือนว่านี่คือแหล่งที่มานี่คือ Emscripten - นำไปและรวบรวม แต่ก็มีห้องสมุดที่ Qemu ขึ้นอยู่กับ และห้องสมุดที่ห้องสมุดเหล่านั้นพึ่งพา ฯลฯ และหนึ่งในนั้นคือ ลิบฟีซึ่งกะล่อนขึ้นอยู่กับ มีข่าวลือทางอินเทอร์เน็ตว่ามีหนึ่งในคอลเลกชันพอร์ตห้องสมุดขนาดใหญ่สำหรับ Emscripten แต่ก็ยากที่จะเชื่อ: ประการแรกไม่ได้ตั้งใจให้เป็นคอมไพเลอร์ใหม่ ประการที่สอง มันเป็นระดับต่ำเกินไป ไลบรารี่เพื่อหยิบและคอมไพล์เป็น JS และไม่ใช่แค่เรื่องของการเพิ่มแอสเซมบลีเท่านั้น ถ้าคุณบิดมัน สำหรับรูปแบบการเรียกบางอย่าง คุณสามารถสร้างอาร์กิวเมนต์ที่จำเป็นบนสแต็กและเรียกใช้ฟังก์ชันโดยไม่มีอาร์กิวเมนต์เหล่านั้นได้ แต่ Emscripten เป็นสิ่งที่ยุ่งยาก: เพื่อให้โค้ดที่สร้างขึ้นดูคุ้นเคยกับเครื่องมือเพิ่มประสิทธิภาพกลไก JS ของเบราว์เซอร์ จึงมีการใช้เทคนิคบางอย่าง โดยเฉพาะอย่างยิ่งสิ่งที่เรียกว่าการวนซ้ำ - ตัวสร้างโค้ดที่ใช้ LLVM IR ที่ได้รับพร้อมกับคำแนะนำในการเปลี่ยนเชิงนามธรรมพยายามสร้าง ifs, loops ที่น่าเชื่อถือขึ้นใหม่ อาร์กิวเมนต์ถูกส่งผ่านไปยังฟังก์ชันอย่างไร? โดยทั่วไปแล้ว อาร์กิวเมนต์ของฟังก์ชัน JS คือ ถ้าเป็นไปได้ ไม่ใช่ผ่านสแต็ก

ในตอนแรกมีแนวคิดที่จะเขียนการแทนที่ libffi ด้วย JS และรันการทดสอบมาตรฐาน แต่สุดท้ายฉันก็สับสนเกี่ยวกับวิธีการสร้างไฟล์ส่วนหัวของฉันเพื่อที่พวกเขาจะทำงานกับโค้ดที่มีอยู่ - ฉันจะทำอย่างไร อย่างที่พวกเขาพูดว่า “งานมันซับซ้อนขนาดนั้นเลยเหรอ “เราโง่ขนาดนั้นเลยเหรอ?” ฉันต้องย้ายพอร์ต libffi ไปยังสถาปัตยกรรมอื่น ดังนั้นโชคดีที่ Emscripten มีทั้งมาโครสำหรับการประกอบแบบอินไลน์ (ใน Javascript ใช่แล้ว ไม่ว่าจะเป็นสถาปัตยกรรมใดก็ตาม ดังนั้นแอสเซมเบลอร์) และความสามารถในการเรียกใช้โค้ดที่สร้างขึ้นได้ทันที โดยทั่วไป หลังจากซ่อมแซมชิ้นส่วน libffi ที่ขึ้นกับแพลตฟอร์มมาระยะหนึ่งแล้ว ฉันก็ได้รับโค้ดที่คอมไพล์ได้และรันมันในการทดสอบครั้งแรกที่ฉันเจอ ฉันประหลาดใจมากที่การทดสอบประสบความสำเร็จ ตะลึงกับอัจฉริยะของฉัน - ไม่ตลกเลย มันใช้งานได้ตั้งแต่การเปิดตัวครั้งแรก - ฉันยังไม่เชื่อสายตาตัวเองเลยไปดูโค้ดผลลัพธ์อีกครั้งเพื่อประเมินว่าจะขุดที่ไหนต่อไป ที่นี่ฉันแทบบ้าเป็นครั้งที่สอง - สิ่งเดียวที่ฟังก์ชั่นของฉันทำคือ ffi_call - สิ่งนี้รายงานว่าการโทรสำเร็จ ไม่มีการเรียกตัวเอง ดังนั้นฉันจึงส่งคำขอดึงครั้งแรกซึ่งแก้ไขข้อผิดพลาดในการทดสอบที่ชัดเจนสำหรับนักเรียนโอลิมปิกคนใดคนหนึ่ง - ไม่ควรเปรียบเทียบจำนวนจริงเป็น a == b และอย่างไร a - b < EPS - คุณต้องจำโมดูลด้วยไม่เช่นนั้น 0 จะเท่ากับ 1/3 อย่างมาก... โดยทั่วไปแล้วฉันพบพอร์ต libffi บางตัวซึ่งผ่านการทดสอบที่ง่ายที่สุดและ glib ใดคือ เรียบเรียงแล้ว - ฉันตัดสินใจว่าจำเป็น ฉันจะเพิ่มในภายหลัง เมื่อมองไปข้างหน้าฉันจะบอกว่าเมื่อปรากฎว่าคอมไพเลอร์ไม่ได้รวมฟังก์ชัน libffi ไว้ในโค้ดสุดท้ายด้วยซ้ำ

แต่อย่างที่ฉันได้กล่าวไปแล้วว่ามีข้อ จำกัด บางประการและในบรรดาการใช้งานพฤติกรรมที่ไม่ได้กำหนดต่างๆฟรีคุณลักษณะที่ไม่พึงประสงค์ยิ่งกว่านั้นได้ถูกซ่อนไว้ - JavaScript ตามการออกแบบไม่รองรับมัลติเธรดพร้อมหน่วยความจำที่ใช้ร่วมกัน โดยหลักการแล้ว สิ่งนี้สามารถเรียกได้ว่าเป็นแนวคิดที่ดี แต่ไม่ใช่สำหรับโค้ดพอร์ตที่มีสถาปัตยกรรมเชื่อมโยงกับเธรด C โดยทั่วไปแล้ว Firefox กำลังทดลองใช้การสนับสนุนผู้ทำงานร่วมกัน และ Emscripten ก็มีการใช้งาน pthread สำหรับพวกเขา แต่ฉันไม่ต้องการพึ่งพามัน ฉันต้องค่อยๆ รูทมัลติเธรดจากโค้ด Qemu นั่นคือค้นหาว่าเธรดทำงานอยู่ที่ใด ย้ายเนื้อความของลูปที่ทำงานในเธรดนี้ไปยังฟังก์ชันแยกต่างหาก และเรียกใช้ฟังก์ชันดังกล่าวทีละรายการจากลูปหลัก

ลองครั้งที่สอง

เมื่อถึงจุดหนึ่ง ก็ชัดเจนว่าปัญหายังคงมีอยู่ และการดันไม้ค้ำยันไปรอบๆ รหัสอย่างไม่ได้ตั้งใจจะไม่ทำให้เกิดผลดีใดๆ เลย สรุป: เราจำเป็นต้องจัดระบบกระบวนการเพิ่มไม้ค้ำยัน เลยเอาเวอร์ชั่น 2.4.1 ที่ยังใหม่อยู่ ณ ขณะนั้นมา (ไม่ใช่ 2.5.0 เพราะใครจะรู้ในเวอร์ชั่นใหม่จะมีจุดบกพร่องที่ยังตรวจไม่พบและผมก็มีจุดบกพร่องของตัวเองมากพอแล้ว ) และสิ่งแรกคือเขียนใหม่อย่างปลอดภัย thread-posix.c. นั่นคือปลอดภัย: หากมีคนพยายามดำเนินการที่นำไปสู่การบล็อกฟังก์ชันนั้นจะถูกเรียกใช้ทันที abort() - แน่นอนว่านี่ไม่ได้แก้ปัญหาทั้งหมดในคราวเดียว แต่อย่างน้อยก็ยังดีกว่าการรับข้อมูลที่ไม่สอดคล้องกันอย่างเงียบๆ

โดยทั่วไป ตัวเลือก Emscripten จะมีประโยชน์มากในการย้ายโค้ดไปยัง JS -s ASSERTIONS=1 -s SAFE_HEAP=1 - ตรวจจับพฤติกรรมที่ไม่ได้กำหนดบางประเภท เช่น การเรียกไปยังที่อยู่ที่ไม่สอดคล้องกัน (ซึ่งไม่สอดคล้องกับโค้ดสำหรับอาร์เรย์ที่พิมพ์เลย เช่น HEAP32[addr >> 2] = 1) หรือการเรียกใช้ฟังก์ชันด้วยจำนวนอาร์กิวเมนต์ที่ไม่ถูกต้อง

อย่างไรก็ตาม ข้อผิดพลาดในการจัดตำแหน่งเป็นปัญหาแยกต่างหาก ดังที่ฉันได้กล่าวไปแล้ว Qemu มีแบ็กเอนด์การตีความที่ "เสื่อมถอย" สำหรับการสร้างโค้ด TCI (ตัวแปลโค้ดขนาดเล็ก) และเพื่อสร้างและรัน Qemu บนสถาปัตยกรรมใหม่ หากคุณโชคดี คอมไพเลอร์ C ก็เพียงพอแล้ว "ถ้าคุณโชคดี". ฉันโชคไม่ดี และปรากฎว่า TCI ใช้การเข้าถึงที่ไม่สอดคล้องกันเมื่อแยกวิเคราะห์โค้ดไบต์ นั่นคือใน ARM ทุกประเภทและสถาปัตยกรรมอื่นๆ ที่มีการเข้าถึงระดับที่จำเป็น Qemu คอมไพล์เนื่องจากมีแบ็กเอนด์ TCG ปกติที่สร้างโค้ดเนทีฟ แต่ TCI จะทำงานกับพวกมันได้หรือไม่นั้นเป็นอีกคำถามหนึ่ง อย่างไรก็ตาม เมื่อปรากฏว่า เอกสาร TCI ระบุสิ่งที่คล้ายกันอย่างชัดเจน เป็นผลให้มีการเพิ่มการเรียกใช้ฟังก์ชันสำหรับการอ่านที่ไม่สอดคล้องกันในโค้ด ซึ่งถูกค้นพบในส่วนอื่นของ Qemu

การทำลายล้างกอง

เป็นผลให้การเข้าถึง TCI ที่ไม่สอดคล้องกันได้รับการแก้ไข วงหลักถูกสร้างขึ้นซึ่งในทางกลับกันเรียกว่าโปรเซสเซอร์ RCU และสิ่งเล็ก ๆ อื่น ๆ ดังนั้นฉันจึงเปิดตัว Qemu พร้อมตัวเลือก -d exec,in_asm,out_asmซึ่งหมายความว่าคุณต้องบอกว่าบล็อกของโค้ดใดที่กำลังดำเนินการอยู่ และในขณะที่ออกอากาศเพื่อเขียนว่ารหัสแขกคืออะไร รหัสโฮสต์คืออะไร (ในกรณีนี้คือ ไบต์โค้ด) มันเริ่มต้น ดำเนินการบล็อกการแปลหลายบล็อก เขียนข้อความการดีบักที่ฉันทิ้งไว้ซึ่ง RCU จะเริ่มทำงานและ... ขัดข้อง abort() ภายในฟังก์ชัน free(). โดยการซ่อมฟังก์ชั่น free() เราพบว่าในส่วนหัวของบล็อกฮีปซึ่งอยู่ในแปดไบต์ก่อนหน่วยความจำที่จัดสรร แทนที่จะเป็นขนาดบล็อกหรืออะไรที่คล้ายกัน กลับกลายเป็นขยะ

การทำลายฮีป - น่ารักจริงๆ... ในกรณีเช่นนี้ มีวิธีแก้ไขที่มีประโยชน์ - จาก (ถ้าเป็นไปได้) แหล่งเดียวกัน ให้รวบรวมไบนารีดั้งเดิมและรันภายใต้ Valgrind หลังจากนั้นไม่นาน ไบนารี่ก็พร้อม ฉันเปิดใช้งานด้วยตัวเลือกเดียวกัน - มันหยุดทำงานแม้ในระหว่างการเริ่มต้นก่อนที่จะถึงการดำเนินการจริง แน่นอนว่ามันไม่เป็นที่พอใจ - เห็นได้ชัดว่าแหล่งที่มาไม่เหมือนกันทุกประการซึ่งไม่น่าแปลกใจเพราะ การกำหนดค่าค้นหาตัวเลือกที่แตกต่างกันเล็กน้อย แต่ฉันมี Valgrind - ก่อนอื่นฉันจะแก้ไขข้อผิดพลาดนี้ จากนั้นหากฉันโชคดี อันเดิมก็จะปรากฏขึ้นมา ฉันกำลังใช้งานแบบเดียวกันภายใต้ Valgrind... เย้ เย้ เย้ มันเริ่มต้นแล้ว ผ่านการรีเซ็ตตามปกติและก้าวข้ามจุดบกพร่องเดิมโดยไม่มีคำเตือนแม้แต่ครั้งเดียวเกี่ยวกับการเข้าถึงหน่วยความจำที่ไม่ถูกต้อง ไม่ต้องพูดถึงเรื่องการล้ม อย่างที่พวกเขาพูดกันว่าชีวิตไม่ได้เตรียมฉันให้พร้อมสำหรับสิ่งนี้ - โปรแกรมที่หยุดทำงานจะหยุดทำงานเมื่อเปิดตัวภายใต้ Walgrind สิ่งที่มันเป็นปริศนา สมมติฐานของฉันคือครั้งหนึ่งในบริเวณใกล้เคียงกับคำสั่งปัจจุบันหลังจากเกิดข้อขัดข้องระหว่างการเริ่มต้น gdb ก็แสดงการทำงาน memset-a พร้อมตัวชี้ที่ถูกต้องโดยใช้อย่างใดอย่างหนึ่ง mmx, หรือ xmm registers แล้วบางทีอาจเป็นข้อผิดพลาดในการจัดตำแหน่ง แม้ว่าจะยังยากที่จะเชื่อก็ตาม

โอเค ดูเหมือนว่า Valgrind จะไม่ช่วยอะไรที่นี่ และนี่คือสิ่งที่น่ารังเกียจที่สุดเริ่มต้นขึ้น - ดูเหมือนว่าทุกอย่างจะเริ่มต้นขึ้นด้วยซ้ำ แต่เกิดข้อผิดพลาดโดยไม่ทราบสาเหตุอย่างแน่นอนเนื่องจากเหตุการณ์ที่อาจเกิดขึ้นเมื่อหลายล้านคำสั่งที่ผ่านมา เป็นเวลานานแล้วที่ยังไม่ชัดเจนว่าจะเข้าใกล้อย่างไร สุดท้ายฉันก็ยังต้องนั่งลงและแก้ไขจุดบกพร่อง การพิมพ์สิ่งที่ส่วนหัวถูกเขียนใหม่แสดงให้เห็นว่ามันไม่ได้ดูเหมือนตัวเลข แต่เป็นข้อมูลไบนารีบางประเภท และดูเถิด พบสตริงไบนารี่นี้ในไฟล์ BIOS นั่นคือตอนนี้สามารถพูดได้อย่างมั่นใจว่าเป็นบัฟเฟอร์ล้น และชัดเจนว่ามันถูกเขียนลงในบัฟเฟอร์นี้ด้วยซ้ำ ถ้าอย่างนั้นอะไรทำนองนี้ - ใน Emscripten โชคดีที่ไม่มีการสุ่มพื้นที่ที่อยู่ไม่มีช่องว่างดังนั้นคุณสามารถเขียนที่ไหนสักแห่งตรงกลางโค้ดเพื่อส่งออกข้อมูลตามตัวชี้จากการเปิดตัวครั้งล่าสุด ดูข้อมูล ดูที่พอยน์เตอร์ และถ้าไม่เปลี่ยนแปลง ให้กินอาหารสมอง จริงอยู่ที่การเชื่อมโยงจะใช้เวลาสองสามนาทีหลังจากการเปลี่ยนแปลง แต่คุณจะทำอย่างไร? เป็นผลให้พบบรรทัดเฉพาะที่คัดลอก BIOS จากบัฟเฟอร์ชั่วคราวไปยังหน่วยความจำของแขก - และแท้จริงแล้วมีพื้นที่ในบัฟเฟอร์ไม่เพียงพอ การค้นหาแหล่งที่มาของที่อยู่บัฟเฟอร์แปลก ๆ นั้นส่งผลให้เกิดฟังก์ชัน qemu_anon_ram_alloc ในไฟล์ oslib-posix.c - ตรรกะมีอยู่ดังนี้: บางครั้งการจัดที่อยู่ให้มีขนาดหน้าใหญ่ 2 MB อาจมีประโยชน์สำหรับสิ่งนี้เราจะถาม mmap ขั้นแรกอีกสักหน่อยแล้วเราจะคืนส่วนที่เกินพร้อมความช่วยเหลือ munmap. และหากไม่จำเป็นต้องจัดตำแหน่งดังกล่าว เราจะระบุผลลัพธ์แทน 2 MB getpagesize() - mmap มันจะยังคงให้ที่อยู่ที่สอดคล้องกัน... ดังนั้นใน Emscripten mmap แค่โทร mallocแต่แน่นอนว่ามันไม่สอดคล้องกับหน้า โดยทั่วไปแล้ว จุดบกพร่องที่ทำให้ฉันหงุดหงิดเป็นเวลาสองสามเดือนได้รับการแก้ไขด้วยการเปลี่ยนแปลง двух เส้น

คุณสมบัติของฟังก์ชั่นการโทร

และตอนนี้โปรเซสเซอร์กำลังนับบางสิ่งบางอย่าง Qemu ไม่มีปัญหา แต่หน้าจอไม่เปิดขึ้นและโปรเซสเซอร์จะเข้าสู่ลูปอย่างรวดเร็วโดยตัดสินจากเอาต์พุต -d exec,in_asm,out_asm. มีสมมติฐานเกิดขึ้น: การขัดจังหวะตัวจับเวลา (หรือโดยทั่วไป การขัดจังหวะทั้งหมด) มาไม่ถึง และแน่นอนถ้าคุณคลายเกลียวการขัดจังหวะจากแอสเซมบลีดั้งเดิมซึ่งใช้งานได้ด้วยเหตุผลบางประการ คุณจะได้ภาพที่คล้ายกัน แต่นี่ไม่ใช่คำตอบเลย: การเปรียบเทียบร่องรอยที่ออกมาพร้อมกับตัวเลือกข้างต้นแสดงให้เห็นว่าวิถีการดำเนินการแยกจากกันเร็วมาก ที่นี่ต้องบอกว่าการเปรียบเทียบสิ่งที่บันทึกโดยใช้ตัวเรียกใช้งาน emrun การดีบักเอาต์พุตด้วยเอาต์พุตของแอสเซมบลีดั้งเดิมไม่ใช่กระบวนการทางกลที่สมบูรณ์ ฉันไม่ทราบแน่ชัดว่าโปรแกรมที่ทำงานอยู่ในเบราว์เซอร์เชื่อมต่อกันอย่างไร emrunแต่บางบรรทัดในเอาต์พุตกลับกลายเป็นว่ามีการจัดเรียงใหม่ ดังนั้นความแตกต่างในส่วนต่างจึงไม่ใช่เหตุผลที่จะสันนิษฐานได้ว่าวิถีได้แยกออกจากกัน โดยทั่วไปปรากฏชัดเจนว่าตามคำแนะนำ ljmpl มีการเปลี่ยนไปใช้ที่อยู่ที่แตกต่างกัน และไบต์โค้ดที่สร้างขึ้นมีความแตกต่างโดยพื้นฐาน: อันหนึ่งมีคำสั่งให้เรียกใช้ฟังก์ชันตัวช่วย ส่วนอีกอันไม่มี หลังจาก googling คำแนะนำและศึกษาโค้ดที่แปลคำสั่งเหล่านี้แล้ว ก็ชัดเจนว่า ประการแรก ทันทีก่อนที่จะอยู่ในทะเบียน cr0 มีการบันทึก - โดยใช้ตัวช่วย - ซึ่งเปลี่ยนโปรเซสเซอร์เป็นโหมดที่ได้รับการป้องกันและประการที่สองว่าเวอร์ชัน js ไม่เคยเปลี่ยนเป็นโหมดที่ได้รับการป้องกัน แต่ความจริงก็คือคุณสมบัติอีกประการหนึ่งของ Emscripten คือการไม่เต็มใจที่จะยอมรับโค้ดเช่นการนำคำสั่งไปใช้ call ใน TCI ซึ่งตัวชี้ฟังก์ชันใดๆ จะให้ผลลัพธ์เป็นประเภท long long f(int arg0, .. int arg9) - ต้องเรียกใช้ฟังก์ชันด้วยจำนวนอาร์กิวเมนต์ที่ถูกต้อง หากกฎนี้ถูกละเมิด ขึ้นอยู่กับการตั้งค่าการดีบัก โปรแกรมอาจหยุดทำงาน (ซึ่งดี) หรือเรียกใช้ฟังก์ชันที่ไม่ถูกต้องเลย (ซึ่งจะต้องเสียใจที่ต้องแก้ไขจุดบกพร่อง) นอกจากนี้ยังมีตัวเลือกที่สาม - เปิดใช้งานการสร้าง wrappers ที่เพิ่ม / ลบอาร์กิวเมนต์ แต่โดยรวมแล้ว wrappers เหล่านี้ใช้พื้นที่มากแม้ว่าที่จริงแล้วฉันต้องการเพียงมากกว่าร้อย wrappers เพียงเล็กน้อยเท่านั้น เพียงอย่างเดียวนี้น่าเศร้ามาก แต่กลับกลายเป็นปัญหาที่ร้ายแรงกว่านี้: ในโค้ดที่สร้างขึ้นของฟังก์ชัน wrapper อาร์กิวเมนต์จะถูกแปลงและแปลง แต่บางครั้งฟังก์ชันที่มีอาร์กิวเมนต์ที่สร้างขึ้นก็ไม่ได้ถูกเรียก - ก็เหมือนกับใน การใช้งาน libffi ของฉัน นั่นคือผู้ช่วยบางคนไม่ได้ถูกประหารชีวิต

โชคดีที่ Qemu มีรายการตัวช่วยที่เครื่องอ่านได้ในรูปแบบของไฟล์ส่วนหัวเช่น

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> ในที่สุดฉันก็มีโอกาสได้ลองใช้ห้องสมุดในที่ทำงาน การแยกวิเคราะห์และมีการเขียนสคริปต์ที่สร้าง wrapper เหล่านั้นสำหรับฟังก์ชันที่ต้องการ

หลังจากนั้นดูเหมือนว่าโปรเซสเซอร์จะทำงานได้ ดูเหมือนว่าจะเป็นเพราะหน้าจอไม่เคยถูกเตรียมใช้งาน แม้ว่า memtest86+ จะสามารถทำงานในแอสเซมบลีดั้งเดิมได้ ในที่นี้มีความจำเป็นต้องชี้แจงว่าโค้ด Qemu block I/O ถูกเขียนด้วย coroutines Emscripten มีการใช้งานที่ยุ่งยากมาก แต่ก็ยังจำเป็นต้องได้รับการสนับสนุนในโค้ด Qemu และคุณสามารถดีบักโปรเซสเซอร์ได้ทันที: Qemu รองรับตัวเลือกต่างๆ -kernel, -initrd, -appendซึ่งคุณสามารถบูต Linux หรือ memtest86+ ได้โดยไม่ต้องใช้อุปกรณ์บล็อกเลย แต่นี่คือปัญหา: ในแอสเซมบลีดั้งเดิมเราสามารถเห็นเอาต์พุตเคอร์เนล Linux ไปยังคอนโซลพร้อมตัวเลือก -nographicและไม่มีเอาต์พุตจากเบราว์เซอร์ไปยังเทอร์มินัลจากตำแหน่งที่เปิดใช้งาน emrun, ไม่ได้มา. นั่นคือไม่ชัดเจน: โปรเซสเซอร์ไม่ทำงานหรือเอาต์พุตกราฟิกไม่ทำงาน แล้วมันก็เกิดขึ้นกับฉันที่จะรอสักหน่อย ปรากฎว่า "โปรเซสเซอร์ไม่ได้หลับ แต่เพียงกระพริบช้าๆ" และหลังจากนั้นประมาณห้านาทีเคอร์เนลก็โยนข้อความจำนวนมากลงบนคอนโซลและยังคงค้างต่อไป เห็นได้ชัดว่าโดยทั่วไปโปรเซสเซอร์ใช้งานได้และเราจำเป็นต้องเจาะลึกโค้ดเพื่อทำงานกับ SDL2 น่าเสียดายที่ฉันไม่รู้วิธีใช้ห้องสมุดนี้ ดังนั้นในบางแห่งฉันต้องดำเนินการแบบสุ่ม เมื่อถึงจุดหนึ่ง เส้น Parallel0 ก็กะพริบบนหน้าจอบนพื้นหลังสีน้ำเงิน ซึ่งบ่งบอกถึงความคิดบางอย่าง ท้ายที่สุดปรากฎว่าปัญหาคือ Qemu เปิดหน้าต่างเสมือนหลายหน้าต่างในหน้าต่างจริงเดียว ซึ่งคุณสามารถสลับได้โดยใช้ Ctrl-Alt-n: มันทำงานได้ในเนทิฟบิลด์ แต่ไม่ใช่ใน Emscripten หลังจากกำจัดหน้าต่างที่ไม่จำเป็นออกไปโดยใช้ตัวเลือกต่างๆ -monitor none -parallel none -serial none และคำแนะนำในการวาดหน้าจอทั้งหมดใหม่ในแต่ละเฟรม ทุกอย่างทำงานกะทันหัน

โครูทีน

ดังนั้นการจำลองในเบราว์เซอร์จึงใช้งานได้ แต่คุณไม่สามารถเรียกใช้ฟลอปปีเดียวที่น่าสนใจได้ เนื่องจากไม่มีบล็อก I/O - คุณต้องใช้การรองรับโครูทีน Qemu มีแบ็กเอนด์ Coroutine หลายตัวอยู่แล้ว แต่เนื่องจากลักษณะของ JavaScript และตัวสร้างโค้ด Emscripten คุณจึงไม่สามารถเริ่มเล่นกลกองซ้อนได้ ดูเหมือนว่า "ทุกอย่างหายไปแล้ว ปูนปลาสเตอร์กำลังถูกเอาออก" แต่นักพัฒนา Emscripten ได้ดูแลทุกอย่างแล้ว สิ่งนี้มีการใช้งานที่ค่อนข้างตลก: มาเรียกใช้การเรียกใช้ฟังก์ชันที่น่าสงสัยกันดีกว่า emscripten_sleep และอื่น ๆ อีกมากมายที่ใช้กลไก Asyncify รวมถึงการเรียกตัวชี้และการเรียกไปยังฟังก์ชันใด ๆ ที่หนึ่งในสองกรณีก่อนหน้านี้อาจเกิดขึ้นต่อไปในสแต็ก และตอนนี้ ก่อนการโทรที่น่าสงสัยแต่ละครั้ง เราจะเลือกบริบทอะซิงโครนัส และทันทีหลังจากการโทร เราจะตรวจสอบว่ามีการเรียกแบบอะซิงโครนัสเกิดขึ้นหรือไม่ และหากมี เราจะบันทึกตัวแปรท้องถิ่นทั้งหมดในบริบทอะซิงก์นี้ ระบุว่าฟังก์ชันใด เพื่อถ่ายโอนการควบคุมไปยังเวลาที่เราต้องการดำเนินการต่อไป และออกจากฟังก์ชันปัจจุบัน นี่คือจุดที่มีขอบเขตในการศึกษาผลกระทบ การสุรุ่ยสุร่าย — สำหรับความต้องการในการเรียกใช้โค้ดอย่างต่อเนื่องหลังจากกลับมาจากการเรียกแบบอะซิงโครนัส คอมไพเลอร์จะสร้าง “stubs” ของฟังก์ชัน โดยเริ่มต้นหลังจากการเรียกที่น่าสงสัย — เช่นนี้: หากมีการเรียกที่น่าสงสัย ฟังก์ชันจะถูกขยายที่ไหนสักแห่ง n/ 2 ครั้ง — ยังคงอยู่ หากไม่ โปรดทราบว่าหลังจากการเรียกแบบอะซิงโครนัสแต่ละครั้ง คุณต้องเพิ่มการบันทึกตัวแปรท้องถิ่นบางตัวให้กับฟังก์ชันดั้งเดิม ต่อจากนั้นฉันยังต้องเขียนสคริปต์ง่ายๆ ใน Python ซึ่งขึ้นอยู่กับชุดของฟังก์ชันที่ใช้งานมากเกินไปโดยเฉพาะอย่างยิ่งซึ่งคาดคะเนว่า "ไม่อนุญาตให้อะซิงโครนัสผ่านตัวเอง" (นั่นคือการเลื่อนระดับสแต็กและทุกสิ่งที่ฉันเพิ่งอธิบายไม่ ทำงานในนั้น) ระบุการโทรผ่านพอยน์เตอร์ที่คอมไพเลอร์ควรละเว้นฟังก์ชันเพื่อไม่ให้ฟังก์ชันเหล่านี้พิจารณาว่าเป็นแบบอะซิงโครนัส แล้วไฟล์ JS ที่มีขนาดต่ำกว่า 60 MB นั้นมากเกินไปอย่างเห็นได้ชัด - สมมติว่าอย่างน้อย 30 แม้ว่าเมื่อฉันตั้งค่าสคริปต์แอสเซมบลีและโยนตัวเลือกตัวเชื่อมโยงออกไปโดยไม่ได้ตั้งใจซึ่งในนั้นก็คือ -O3. ฉันรันโค้ดที่สร้างขึ้น และ Chromium กินหน่วยความจำจนหมดและขัดข้อง จากนั้นฉันก็ดูสิ่งที่เขาพยายามดาวน์โหลดโดยไม่ได้ตั้งใจ... ฉันจะว่าอย่างไรได้ ฉันก็คงหยุดนิ่งเหมือนกันหากฉันถูกขอให้ศึกษาอย่างรอบคอบและเพิ่มประสิทธิภาพ Javascript ที่มีขนาด 500+ MB

น่าเสียดายที่การตรวจสอบในโค้ดไลบรารีสนับสนุน Asyncify นั้นไม่เป็นมิตรเลย longjmp-s ที่ใช้ในโค้ดตัวประมวลผลเสมือน แต่หลังจากแพตช์เล็กๆ ที่ปิดใช้งานการตรวจสอบเหล่านี้และกู้คืนบริบทอย่างจริงจัง ราวกับว่าทุกอย่างเรียบร้อยดี โค้ดก็ใช้งานได้ แล้วสิ่งแปลก ๆ ก็เริ่มขึ้น: บางครั้งการตรวจสอบในรหัสการซิงโครไนซ์ก็ถูกทริกเกอร์ - อันเดียวกับที่ทำให้โค้ดเสียหายหากตามตรรกะการดำเนินการควรถูกบล็อก - มีคนพยายามคว้า mutex ที่บันทึกไว้แล้ว โชคดีที่สิ่งนี้กลับกลายเป็นว่าไม่ใช่ปัญหาเชิงตรรกะในโค้ดซีเรียลไลซ์ - ฉันเพียงใช้ฟังก์ชันลูปหลักมาตรฐานที่ Emscripten มอบให้ แต่บางครั้งการโทรแบบอะซิงโครนัสจะแกะสแต็กออกทั้งหมด และในขณะนั้นก็จะล้มเหลว setTimeout จากลูปหลัก - ดังนั้นโค้ดจึงเข้าสู่การวนซ้ำหลักโดยไม่ออกจากการวนซ้ำครั้งก่อน เขียนใหม่บนวงวนไม่สิ้นสุดและ emscripten_sleepและปัญหาเกี่ยวกับ mutexes ก็หยุดลง โค้ดมีความสมเหตุสมผลมากขึ้น - ท้ายที่สุดแล้วฉันไม่มีโค้ดบางตัวที่เตรียมเฟรมแอนิเมชั่นถัดไป - โปรเซสเซอร์เพิ่งคำนวณบางอย่างและหน้าจอจะได้รับการอัปเดตเป็นระยะ อย่างไรก็ตาม ปัญหาไม่ได้หยุดอยู่แค่นั้น บางครั้งการดำเนินการของ Qemu ก็ยุติลงอย่างเงียบๆ โดยไม่มีข้อยกเว้นหรือข้อผิดพลาดใดๆ ในขณะนั้นฉันก็ยอมแพ้ แต่เมื่อมองไปข้างหน้าฉันจะบอกว่าปัญหาคือ: อันที่จริงรหัส Coroutine ไม่ได้ใช้ setTimeout (หรืออย่างน้อยก็ไม่บ่อยเท่าที่คุณคิด): ฟังก์ชั่น emscripten_yield เพียงตั้งค่าสถานะการโทรแบบอะซิงโครนัส ประเด็นทั้งหมดก็คือ emscripten_coroutine_next ไม่ใช่ฟังก์ชันอะซิงโครนัส: ภายในจะตรวจสอบแฟล็ก รีเซ็ต และถ่ายโอนการควบคุมไปยังตำแหน่งที่ต้องการ นั่นคือการเลื่อนระดับของสแต็กสิ้นสุดลงตรงนั้น ปัญหาคือเนื่องจากการใช้งานฟรี ซึ่งปรากฏขึ้นเมื่อพูล coroutine ถูกปิดใช้งานเนื่องจากความจริงที่ว่าฉันไม่ได้คัดลอกบรรทัดโค้ดที่สำคัญจากแบ็กเอนด์ coroutine ที่มีอยู่ ฟังก์ชันนี้ qemu_in_coroutine กลับเป็นจริงทั้งที่ในความเป็นจริงแล้วควรส่งคืนเท็จ สิ่งนี้นำไปสู่การโทร emscripten_yieldเหนือนั้นไม่มีใครอยู่บนสแต็ก emscripten_coroutine_nextสแต็กคลี่ออกไปจนถึงด้านบนสุด แต่ไม่มี setTimeoutอย่างที่ฉันบอกไปแล้วว่าไม่ได้จัดแสดง

การสร้างโค้ดจาวาสคริปต์

และนี่คือคำสัญญาที่ว่า "คืนเนื้อสับกลับ" ไม่เชิง. แน่นอนว่าหากเรารัน Qemu ในเบราว์เซอร์และ Node.js ในนั้น แน่นอนว่าหลังจากการสร้างโค้ดใน Qemu เราจะได้ JavaScript ผิดโดยสิ้นเชิง แต่ถึงกระนั้นก็มีการเปลี่ยนแปลงแบบย้อนกลับบ้าง

ก่อนอื่น เล็กน้อยเกี่ยวกับวิธีการทำงานของ Qemu โปรดยกโทษให้ฉันทันที: ฉันไม่ใช่นักพัฒนา Qemu มืออาชีพและข้อสรุปของฉันอาจมีข้อผิดพลาดในบางแห่ง ดังที่พวกเขากล่าวว่า “ความคิดเห็นของนักเรียนไม่จำเป็นต้องตรงกับความคิดเห็นของครู สัจพจน์และสามัญสำนึกของ Peano” Qemu มีสถาปัตยกรรมแขกที่รองรับจำนวนหนึ่ง และแต่ละสถาปัตยกรรมก็มีไดเร็กทอรีที่คล้ายกัน target-i386. เมื่อสร้าง คุณสามารถระบุการสนับสนุนสำหรับสถาปัตยกรรมแบบแขกได้หลายแบบ แต่ผลลัพธ์จะเป็นเพียงไบนารีหลายแบบ ในทางกลับกัน โค้ดที่รองรับสถาปัตยกรรมของแขกจะสร้างการดำเนินการ Qemu ภายในบางส่วน ซึ่ง TCG (Tiny Code Generator) จะกลายเป็นโค้ดเครื่องสำหรับสถาปัตยกรรมโฮสต์อยู่แล้ว ตามที่ระบุไว้ในไฟล์ readme ที่อยู่ในไดเร็กทอรี tcg เดิมทีนี่เป็นส่วนหนึ่งของคอมไพเลอร์ C ทั่วไป ซึ่งต่อมาถูกดัดแปลงสำหรับ JIT ตัวอย่างเช่น สถาปัตยกรรมเป้าหมายในแง่ของเอกสารนี้จึงไม่ใช่สถาปัตยกรรมแบบแขกอีกต่อไป แต่เป็นสถาปัตยกรรมโฮสต์ เมื่อถึงจุดหนึ่งองค์ประกอบอื่นก็ปรากฏขึ้น - Tiny Code Interpreter (TCI) ซึ่งควรรันโค้ด (เกือบจะเป็นการดำเนินการภายในเดียวกัน) ในกรณีที่ไม่มีตัวสร้างโค้ดสำหรับสถาปัตยกรรมโฮสต์เฉพาะ ตามที่ระบุไว้ในเอกสารประกอบ ล่ามนี้อาจทำงานได้ไม่ดีเท่ากับตัวสร้างโค้ด JIT เสมอไป ไม่เพียงแต่ในเชิงปริมาณในแง่ของความเร็วเท่านั้น แต่ยังรวมถึงในเชิงคุณภาพด้วย แม้ว่าฉันจะไม่แน่ใจว่าคำอธิบายของเขามีความเกี่ยวข้องอย่างสมบูรณ์ก็ตาม

ตอนแรกฉันพยายามสร้างแบ็กเอนด์ TCG ที่มีคุณสมบัติครบถ้วน แต่เกิดความสับสนอย่างรวดเร็วในซอร์สโค้ดและคำอธิบายคำสั่งไบต์โค้ดที่ไม่ชัดเจนทั้งหมด ดังนั้นฉันจึงตัดสินใจรวมล่าม TCI สิ่งนี้ให้ข้อดีหลายประการ:

  • เมื่อใช้งานตัวสร้างโค้ดคุณไม่สามารถดูคำอธิบายของคำแนะนำได้ แต่ดูที่โค้ดล่าม
  • คุณสามารถสร้างฟังก์ชันได้ไม่ใช่สำหรับบล็อกการแปลทุกบล็อกที่พบ แต่หลังจากการดำเนินการครั้งที่ร้อยเท่านั้น
  • หากโค้ดที่สร้างขึ้นมีการเปลี่ยนแปลง (และดูเหมือนว่าจะเป็นไปได้ โดยตัดสินโดยฟังก์ชันที่มีชื่อที่มีคำว่า patch) ฉันจะต้องทำให้โค้ด JS ที่สร้างขึ้นเป็นโมฆะ แต่อย่างน้อยฉันก็จะมีบางอย่างที่จะสร้างใหม่

สำหรับประเด็นที่สาม ฉันไม่แน่ใจว่าจะสามารถทำการแพตช์ได้หลังจากที่โค้ดถูกดำเนินการเป็นครั้งแรก แต่สองจุดแรกก็เพียงพอแล้ว

เริ่มแรก รหัสถูกสร้างขึ้นในรูปแบบของสวิตช์ขนาดใหญ่ตามที่อยู่ของคำสั่ง bytecode ดั้งเดิม แต่จากนั้น เมื่อนึกถึงบทความเกี่ยวกับ Emscripten การเพิ่มประสิทธิภาพของ JS ที่สร้างขึ้น และการวนซ้ำ ฉันจึงตัดสินใจสร้างโค้ดของมนุษย์มากขึ้น โดยเฉพาะอย่างยิ่งเมื่อสังเกตจากเชิงประจักษ์แล้ว ปรากฎว่าจุดเริ่มต้นเพียงจุดเดียวในบล็อกการแปลคือการเริ่มต้น ไม่ช้ากว่าจะพูดเสร็จ หลังจากนั้นไม่นานเราก็มีตัวสร้างโค้ดที่สร้างโค้ดด้วย ifs (แม้ว่าจะไม่มีลูปก็ตาม) แต่โชคร้ายที่มันพัง โดยแจ้งว่าคำแนะนำมีความยาวไม่ถูกต้อง นอกจากนี้ คำสั่งสุดท้ายในระดับการเรียกซ้ำนี้คือ brcond. โอเค ฉันจะเพิ่มการตรวจสอบที่เหมือนกันให้กับการสร้างคำสั่งนี้ก่อนและหลังการเรียกซ้ำ และ... ไม่มีการดำเนินการใดเลย แต่หลังจากสวิตช์ยืนยัน คำสั่งเหล่านั้นยังคงล้มเหลว ในท้ายที่สุด หลังจากศึกษาโค้ดที่สร้างขึ้น ฉันพบว่าหลังจากสวิตช์ ตัวชี้ไปยังคำสั่งปัจจุบันจะถูกโหลดใหม่จากสแต็ก และอาจถูกเขียนทับด้วยโค้ด JavaScript ที่สร้างขึ้น และมันก็ปรากฏออกมา การเพิ่มบัฟเฟอร์จากหนึ่งเมกะไบต์เป็นสิบไม่ได้นำไปสู่สิ่งใด และเห็นได้ชัดว่าตัวสร้างโค้ดกำลังทำงานเป็นวงกลม เราต้องตรวจสอบว่าเราไม่ได้เกินขอบเขตของวัณโรคในปัจจุบัน และหากทำได้ ให้ออกที่อยู่ของวัณโรคถัดไปด้วยเครื่องหมายลบ เพื่อที่เราจะได้ดำเนินการดำเนินการต่อไปได้ นอกจากนี้ยังช่วยแก้ปัญหา "ฟังก์ชันที่สร้างขึ้นใดที่ควรใช้งานไม่ได้หากโค้ดไบต์นี้มีการเปลี่ยนแปลง" — เฉพาะฟังก์ชันที่สอดคล้องกับบล็อกการแปลนี้เท่านั้นที่ต้องใช้งานไม่ได้ อย่างไรก็ตามแม้ว่าฉันจะแก้ไขทุกอย่างใน Chromium (เนื่องจากฉันใช้ Firefox และง่ายกว่าสำหรับฉันที่จะใช้เบราว์เซอร์แยกต่างหากสำหรับการทดลอง) Firefox ช่วยฉันแก้ไขความไม่เข้ากันกับมาตรฐาน 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 เวอร์ชันที่ทันสมัยกว่า ในระหว่างนี้ มีเธรดในรูปแบบบล็อกใน Gita: สำหรับแต่ละ "ระดับ" ที่ผ่านไปแล้วอย่างน้อยก็มีการเพิ่มคำอธิบายโดยละเอียดในภาษารัสเซีย ที่จริงแล้วบทความนี้เป็นการบอกเล่าข้อสรุปในระดับสูง git log.

คุณสามารถลองได้ทั้งหมด ที่นี่ (ระวังการจราจร).

สิ่งที่กำลังทำงานอยู่:

  • โปรเซสเซอร์เสมือน x86 ทำงานอยู่
  • มีต้นแบบการทำงานของตัวสร้างโค้ด JIT ตั้งแต่โค้ดเครื่องไปจนถึง JavaScript
  • มีเทมเพลตสำหรับประกอบสถาปัตยกรรมแขก 32 บิตอื่นๆ: ตอนนี้คุณสามารถชื่นชม Linux สำหรับสถาปัตยกรรม MIPS ที่ค้างในเบราว์เซอร์ที่ขั้นตอนการโหลด

คุณทำอะไรได้อีก

  • เร่งความเร็วการจำลอง แม้ในโหมด JIT ดูเหมือนว่าจะทำงานช้ากว่า Virtual x86 (แต่อาจมี Qemu ทั้งหมดที่มีฮาร์ดแวร์และสถาปัตยกรรมจำลองจำนวนมาก)
  • เพื่อสร้างอินเทอร์เฟซแบบปกติ พูดตามตรงว่าฉันไม่ใช่นักพัฒนาเว็บที่ดี ดังนั้นตอนนี้ฉันจึงสร้าง Emscripten Shell มาตรฐานใหม่อย่างดีที่สุดเท่าที่จะทำได้
  • ลองเปิดใช้ฟังก์ชัน Qemu ที่ซับซ้อนมากขึ้น เช่น ระบบเครือข่าย การโยกย้าย VM ฯลฯ
  • ยูพีเอส: คุณจะต้องส่งการพัฒนาและรายงานข้อบกพร่องบางส่วนของคุณไปยัง Emscripten อัพสตรีม เช่นเดียวกับที่ผู้ขนย้าย Qemu และโครงการอื่น ๆ ก่อนหน้านี้ทำ ขอขอบคุณพวกเขาที่สามารถใช้การมีส่วนร่วมกับ Emscripten โดยปริยายเป็นส่วนหนึ่งของงานของฉัน

ที่มา: will.com

เพิ่มความคิดเห็น