3 คะแนน โดย GN⁺ 2023-07-15 | 3 ความคิดเห็น | แชร์ทาง WhatsApp
  • เพื่อให้สามารถคอมไพล์ซอฟต์แวร์ C เดิมได้มากขึ้นด้วย Cosmopolitan Libc จึงต้องทำมากกว่าการหลอกระบบบิลด์ โดยต้องใช้ แพตช์ GCC ขนาดราว 2,000 บรรทัด เพื่อให้คอมไพเลอร์แปลงโค้ดที่มีปัญหาโดยอัตโนมัติ
  • อุปสรรคคือ ค่าระดับระบบ อย่าง SIGTERM, EINVAL ไม่ใช่ค่าคงที่ตอนคอมไพล์ ทำให้ชนกับกฎของ C สำหรับป้าย switch case และการกำหนดค่าเริ่มต้นของ static/const struct
  • มีการลองใช้ทั้ง sed, regex ของ Python, ปลั๊กอิน GCC, ไปจนถึงการดักแมโครของพรีโปรเซสเซอร์ แต่เพราะข้อผิดพลาดของป้าย case เกิดขึ้นระหว่างการพาร์ส จึงยากที่จะกันไว้ได้ด้วยปลั๊กอินเพียงอย่างเดียว
  • แพตช์สุดท้ายเปิดใช้ด้วยแฟลก -fportcosmo โดย GCC จะเปลี่ยน switch เป็น if/goto ก่อนจะรายงานข้อผิดพลาด และเสริมการกำหนดค่าเริ่มต้นของ struct ด้วยการเติมค่าขณะรันไทม์
  • แม้ยังมีข้อจำกัดกับ anonymous struct, enum, const int บางส่วน และดัชนีอาร์เรย์ภายในการกำหนดค่าเริ่มต้น แต่ก็เปิดทางให้คอมไพล์ซอฟต์แวร์อย่าง bash, curl, git, ninja, gcc ได้ โดยไม่ต้องแก้ซอร์สโค้ด

เป้าหมายของแพตช์ GCC

  • มีการเขียน แพตช์ gcc ราว 2,000 บรรทัด เพื่อให้สร้าง Actually Portable Executables ด้วย Cosmopolitan Libc ได้ง่ายขึ้น
  • หลังใช้แพตช์แล้ว สามารถใช้ระบบบิลด์ ./configure หรือ cmake เพื่อคอมไพล์ซอฟต์แวร์ต่อไปนี้ได้
    • bash
    • curl
    • git
    • ninja
    • gcc เอง
  • ไฟล์ปฏิบัติการที่คอมไพล์ได้ควรสามารถรันบน Linux, FreeBSD, MacOS, OpenBSD, NetBSD และ Windows ได้
    • มีข้อสังเกตว่าไม่ได้ทดสอบการรันบน Windows โดยตรง
  • ไบนารีที่บิลด์ผ่าน GitHub Actions สามารถดาวน์โหลดได้จาก superconfigure releases

จุดที่ติดขัดในการพอร์ต Cosmopolitan Libc

  • หลังจากที่ Cosmopolitan Libc ได้รับความสนใจจากเว็บเซิร์ฟเวอร์ redbean ในปี 2021 คำถามสำคัญก็คือซอฟต์แวร์ C เดิมจะทำงานบน libc นี้ได้ดีแค่ไหน
  • ระหว่างการพอร์ต language runtime อย่าง Lua, Wren, Janet และ quickjs ก็พบรูปแบบปัญหาเดิมซ้ำ ๆ
  • เมื่อใช้ ค่าคงที่ของระบบ อย่าง SIGTERM, EINVAL เป็นป้าย case ในคำสั่ง switch ก็จะชนกับกฎการคอมไพล์ของ C
  • มาตรฐาน C กำหนดให้ป้าย case ต้องเป็น ค่าคงที่ตอนคอมไพล์
    • ใน Cosmopolitan Libc แมโคร C preprocessor ที่เกี่ยวกับ system interface จำเป็นต้องเป็น symbolic
    • ผลคือโค้ดที่พบได้ทั่วไปอย่าง switch(errno) { case EINVAL: ... } อาจคอมไพล์ไม่ผ่าน

เปลี่ยน switch ให้เป็น if และ goto

  • หากป้ายอย่าง case EINVAL, case ENOSYS ภายใน switch(errno) ไม่ใช่ค่าคงที่ตอนคอมไพล์ GCC จะรายงานข้อผิดพลาด case label does not reduce to an integer constant
  • แพตช์นี้จะแปลงคำสั่ง switch โดยอัตโนมัติให้เป็นโครงสร้างต่อไปนี้
    • เพิ่มแขนงในรูป if (errno == EINVAL) goto caselabel_EINVAL; สำหรับค่า case แต่ละตัว
    • เพิ่ม goto เพื่อย้ายไปยังป้าย default
    • ปรับบล็อก case เดิมใหม่ด้วยป้ายและ goto endofthis_switch
  • วิธีนี้อาจไม่ใช่วิธีที่สวยที่สุด แต่สามารถจัดการกรณีที่พบในหลายโค้ดเบสได้
  • เพราะต้องรองรับ switch ที่มีทั้ง ifdef และ fallthrough ปะปนกันอยู่ การแปลงแบบอิง goto จึงเป็นตัวเลือกที่ตรงไปตรงมา

การกำหนดค่าเริ่มต้นของ struct ก็มีข้อจำกัดเดียวกัน

  • ปัญหาเรื่องค่าคงที่ตอนคอมไพล์ยังเกิดกับการกำหนดค่าเริ่มต้นของ static หรือ const struct ด้วย
  • หาก EINVAL ไม่ใช่ค่าคงที่ตอนคอมไพล์ การกำหนดค่าเริ่มต้นบางแบบต่อไปนี้จะไม่ถูกต้องตาม C
    • การกำหนดค่าเริ่มต้นของ struct โลคัลทั่วไปภายในฟังก์ชันยังทำได้
    • const struct
    • static struct
    • const struct ระดับโกลบอล
    • static struct ระดับโกลบอล
  • ตัวอย่างจริงคือโมดูล faulthandler ของ CPython
  • วิธีแก้คือกำหนดค่าเริ่มต้นของ struct ด้วยค่าหลอกก่อน แล้วเพิ่มโค้ดให้เติมค่าที่ถูกต้องก่อนใช้งาน
    • ภายในฟังก์ชันจะเพิ่มคำสั่ง if
    • สำหรับการกำหนดค่าเริ่มต้นแบบโกลบอลหรือแบบสแตติก จะใช้ __attribute__((constructor))

ข้อจำกัดของการแทนที่ข้อความและปลั๊กอิน GCC

  • ตอนแรกมีความพยายามทำการแปลงอัตโนมัติด้วยสคริปต์เชลล์ sed จากนั้นจึงลองสคริปต์ Python และ regex เพื่อรองรับ fallthrough ด้วย
  • แต่เพราะ C preprocessor และ ifdef การแทนที่ข้อความจึงทำงานได้ไม่สมบูรณ์
    • เนื่องจากยากที่จะรู้ล่วงหน้าว่าระหว่างคอมไพล์ ifdef ใดจะถูกเปิดใช้งาน
  • ความพยายามถัดมาคือใช้ AST transformation ผ่านปลั๊กอิน GCC
    • ปลั๊กอิน GCC สามารถโหลดได้ด้วย -fplugin=your-plugin.so
    • สามารถเข้าถึง AST ได้ในอีเวนต์อย่าง PLUGIN_PRE_GENERICIZE, PLUGIN_FINISH_PARSE_FUNCTION, PLUGIN_FINISH_DECL
    • สามารถพิมพ์ AST ด้วย debug_tree และเดิน AST ด้วย walk_tree_without_duplicates
  • ปัญหาคือการตรวจสอบป้าย case เกิดขึ้นระหว่างที่ GCC กำลังพาร์ส
    • ปลั๊กอินจะเข้าถึง AST ได้หลังพาร์สเสร็จแล้ว
    • ป้ายที่ผิดอย่าง case SIGTERM: จะไม่เหลืออยู่ใน AST
    • เพราะ GCC รายงานข้อผิดพลาดไปแล้ว จึงแก้จากฝั่งปลั๊กอินได้ยาก

การทดลองดักแมโครของพรีโปรเซสเซอร์

  • เมื่อปลั๊กอินเพียงอย่างเดียวกันข้อผิดพลาดก่อนพาร์สได้ยาก จึงลองหาวิธีโต้ตอบกับ C preprocessor
  • ผ่านโครงสร้าง cpp_reader ในเฮดเดอร์ของปลั๊กอิน GCC จึงสามารถผูก callback ตอนมี #define, #undef และตอนมีการใช้แมโครได้
  • มีการใช้ประโยชน์จากโครงสร้างของแมโครค่าระบบใน Cosmopolitan Libc เพื่อแทรกค่าคงที่ชั่วคราว
    • ตัวอย่าง: extern const int SIGTERM;
    • ตัวอย่าง: #define SIGTERM SYMBOLIC(SIGTERM)
    • สำหรับปลั๊กอินมีค่าชั่วคราวอย่าง static const int __tmpcosmo_SIGTERM = ...
  • ปลั๊กอินจะดักการใช้ SYMBOLIC(SIGTERM) แล้วแทนด้วยค่าชั่วคราว จากนั้นในขั้น PLUGIN_PRE_GENERICIZE จึงค่อยแปลง AST กลับเป็น VAR_DECL ที่ถูกต้อง
  • การแฮ็กแมโครนี้ ช่วยเลี่ยงข้อผิดพลาดของ switch และการกำหนดค่าเริ่มต้นของ struct ได้สำเร็จ
  • วิธีนี้ทำให้สามารถคอมไพล์ CPython 3.11 ได้อย่างน้อย

เหตุผลที่แก้ GCC โดยตรงแทนการใช้ปลั๊กอิน

  • หลังหารือกับ Justine Tunney และยืนยันได้ว่าปัญหานี้แก้ได้ จึงมีการเสนอแนวทางที่ง่ายกว่าคือแก้โค้ดเบสของ GCC โดยตรง
  • ปลั๊กอินและการแฮ็กแมโครมีงานส่วนเกินมาก และทำให้การจัดการ edge case กับการล่มซับซ้อนขึ้น
  • ตัวอย่างเช่น แม้จะต้องแปลงแค่ case ENOSYS แต่เพราะการแทนที่แมโคร ปลั๊กอินกลับต้องจัดการการใช้งานปกติอย่าง ENOSYS + 1 หรือ printf("ENOSYS = %d", ENOSYS) ด้วย
  • แนวทางแพตช์ GCC จะเปิดใช้ฟีเจอร์ด้วย แฟลก -fportcosmo แทน -fplugin แยกต่างหาก
  • ก่อนที่ GCC จะรายงานข้อผิดพลาด case is not constant มันจะตรวจว่ามีการเปิด flag_portcosmo หรือไม่ และถ้าเปิดไว้ก็จะทำการแทนที่ที่จำเป็น
  • โค้ดแปลง AST จากปลั๊กอินเดิมถูกย้ายเข้ามาให้ทำงานภายใน GCC ก่อนเรียก callback ของปลั๊กอินอื่น
  • การเปลี่ยนแปลงนี้ทำให้สามารถลบโค้ดแฮ็กที่เกี่ยวกับแมโครออกได้

การบิลด์จริงและการขยายแพตช์

  • ณ วันที่ 5 มิถุนายน 2023 GCC ที่ถูกแพตช์ผ่านชุดทดสอบทั้งหมดของปลั๊กอินก่อนหน้า และมีการเพิ่มไบนารีใหม่เข้าใน monorepo ของ Cosmopolitan Libc
  • ระหว่างที่คอมไพล์โค้ดมากขึ้นก็มีการแก้ไขและปรับปรุงต่อเนื่อง
  • การบิลด์ lua ง่ายขึ้น และยังสามารถหาตำแหน่งที่ g++ เจอข้อผิดพลาด case constant จนทำให้คอมไพล์ ninja ได้ด้วย
  • ระหว่างพยายามเพิ่ม ncurses ให้กับ Python 3.11 ก็พบปัญหาใหม่
    • ใน ncurses มีโค้ดอย่าง DATA2(PARODD | PARENB, PARODD) ซึ่งมี นิพจน์ทวิภาค อยู่ในค่ากำหนดเริ่มต้นของ struct
    • แพตช์เดิมรองรับการกำหนดค่าเริ่มต้นด้วยค่าคงที่แบบง่าย แต่ยังรองรับนิพจน์อย่าง PARODD | PARENB ไม่ได้
  • หลังจากนั้นจึงขยายแพตช์ให้ป้าย case และสมาชิกในการกำหนดค่าเริ่มต้นของ struct ในภาษา C สามารถเป็นนิพจน์ตามอำเภอใจได้
    • ตามมาตรฐาน C ป้าย case ยังคงต้องเป็นค่าคงที่ตอนคอมไพล์
    • คอมไพเลอร์ที่ถูกแพตช์จะเตือนเมื่อมีการใช้งานแบบนี้ แต่ยังคงคอมไพล์ต่อ
    • C++ ถูกยกเว้นจากการแทนที่นิพจน์ตามอำเภอใจ เพราะไม่สอดคล้องกับการจัดการ constexpr ของ g++11
  • หลังการเปลี่ยนแปลงนี้ ncurses ก็สามารถคอมไพล์ได้โดยไม่มีปัญหา

ข้อจำกัดที่ยังเหลือและบทสรุปเชิงปฏิบัติ

  • แพตช์นี้ยังไม่สมบูรณ์
    • ยังจัดการ anonymous struct บางกรณีไม่ได้
    • ยังจัดการ enum และบางกรณีของ const int ไม่ได้
    • ยังจัดการกรณีที่ใช้ SIGTERM เป็นดัชนีอาร์เรย์ภายในการกำหนดค่าเริ่มต้นไม่ได้
    • ยังอาจมีการล่มของคอมไพเลอร์เป็นครั้งคราวในกรณีอย่างการกำหนดค่าเริ่มต้น static ที่แปลก ๆ หรือการใส่ SIGTERM ลงใน static const int8_t
  • ถึงอย่างนั้นก็สามารถกำจัดตัวอย่างโต้แย้งที่ชัดเจนได้จำนวนมาก และทำให้ซอฟต์แวร์ยอดนิยมหลายตัวคอมไพล์ได้อย่างราบรื่น
  • สภาพแวดล้อมทดสอบที่เข้มงวดกว่านี้อาจเผยให้เห็นจุดที่ควรปรับปรุงเพิ่ม
  • หากต้องการก็ยังสามารถแก้คำสั่ง switch และนิพจน์กำหนดค่าเริ่มต้นของ struct ด้วยมือได้ แต่ในหลายกรณีคอมไพเลอร์สามารถจัดการให้อัตโนมัติได้
  • หากซอฟต์แวร์ C สามารถคอมไพล์แบบสแตติกได้ และโดยเฉพาะอย่างยิ่งถ้าคอมไพล์ด้วย musl ได้ ก็มีโอกาสสูงว่าจะคอมไพล์ด้วย Cosmopolitan Libc ได้เช่นกัน

3 ความคิดเห็น

 
ryuheechul 2023-07-15

ตอนนี้ neo จะใช้รูปแบบที่ครอบข้อความด้วยโค้ดบล็อกจริง ๆ แล้วเหรอ? ที่ต้องเลื่อนแนวนอนค่อนข้างน่าเสียดาย แล้วก็สีข้อความโทนแดงก็แอบน่าเสียดายนิดหน่อย...

 
xguru 2023-07-16

อ๊ะ การถูกครอบด้วยโค้ดบล็อกเป็นบั๊กครับ ฮือ

 
GN⁺ 2023-07-15
ความคิดเห็นบน Hacker News
  • ฉันเป็นคนเขียนบทความนี้เอง และชื่อควรจะเป็น “Patching GCC to build Actually Portable Executables” เพราะมันอ้างถึงฟอร์แมต Actually Portable Executable ของ Cosmopolitan Libc และ jart
    ตอนนี้ด้วยแพตช์ GCC ของฉัน เราสามารถบิลด์ซอฟต์แวร์อย่าง vim, emacs, ninja, bash, git และ gcc ด้วย Cosmopolitan Libc ผ่านระบบบิลด์แบบ autotools/cmake ตามปกติได้แล้ว ไฟล์ executable ที่ได้ควรจะรันได้บน Linux, FreeBSD, MacOS, OpenBSD, NetBSD และ Windows แต่ยังไม่ได้ทดสอบบน Windows
    รายชื่อซอฟต์แวร์ที่ลองบิลด์ด้วยวิธีนี้มีอยู่ที่นี่: https://github.com/ahgamut/superconfigure
    สคริปต์ superconfigure เป็นแค่ตัวห่อครอบ configure script ทั่วไปที่ใช้บิลด์ซอฟต์แวร์ และใส่แฟล็กอย่าง --enable-static ให้
    ถ้าอยากลองบิลด์ GCC ด้วย Cosmopolitan Libc ให้ใช้รีโพซิทอรีนี้ได้: https://github.com/ahgamut/musl-cross-make/tree/gccbuild

    • สงสัยว่าแพตช์นี้มีโอกาสเข้า GCC upstream ไหม APE executable และ cosmopolitan libc เป็นเทคโนโลยีที่เจ๋งมาก และคงดีถ้าใช้งานได้ง่ายขึ้น
    • เยี่ยมเลย สงสัยว่ามันมีโอกาสมีปัญหาไหมถ้าใช้สิ่งนี้อยู่เบื้องหลังภาษาที่ transpile อย่าง Nim
    • อยากรู้ว่าได้ลองบิลด์ busybox หรือยัง
    • ไม่นานมานี้เห็นว่าถูกแบนจาก lobste.rs เพราะถูกมองว่าเป็น sockpuppet ของ jart แต่เพิ่งมารู้ตอนนี้ว่าเป็นคนละคนกันจริง ๆ
      หวังว่าจะได้รับการปฏิบัติที่ดีกว่าบน HN
    • แค่บิลด์ผ่านได้อย่างเดียวอาจยังไม่ใช่ความสำเร็จที่ใหญ่ขนาดนั้น สิ่งที่น่าสนใจกว่าคือมันทำงานได้จริงหรือไม่
      เช่น เมื่อเทียบกับเวอร์ชัน glibc อยากรู้ว่ามี การทดสอบที่เพียงพอ มากพอจะยืนยันได้ไหมว่าไม่มี regression
  • มี issue ที่เกี่ยวข้องเรื่องนี้อยู่ในรีโพซิทอรี Go: https://github.com/golang/go/issues/51900

  • โดยรวมถือว่าเจ๋งมาก แต่ผมสงสัยเรื่องพื้นฐานทางทฤษฎีของมัน ถ้าจะสร้าง target ระบบปฏิบัติการใหม่ ตามตรรกะแล้วน่าจะเหมาะกว่าที่จะนิยาม ค่าคงที่ชุดใหม่
    แทนที่จะส่งค่าคงที่ของแต่ละระบบปฏิบัติการลูกต่อไปตามเดิม ก็แปลงค่าคงที่ของแต่ละระบบให้กลายเป็นค่าเดียวกันได้ทุกที่ แบบนั้นโค้ดที่ใช้ค่าคงที่เป็นดัชนีอาร์เรย์หรือใน case statement ก็จะทำงานได้ตามปกติ
    วิธีนี้อาจมี overhead มากกว่าแนวทางที่อธิบายในบทความ แต่ดูง่ายกว่ามาก และสำหรับคนที่อยากคอมไพล์โปรแกรมใหม่ให้ใช้ Cosmopolitan libc ก็น่าจะใช้ง่ายกว่า

    • ถ้าสร้างค่าคงที่ชุดใหม่ การคอมไพล์จะง่ายขึ้นมากก็จริง แต่ส่วนตัวคิดว่า overhead จากการแปลงไปมาระหว่างค่าคงที่เดิมกับค่าคงที่ใหม่ตอนรันไทม์สูงเกินไป
      ดูรายการค่าคงที่ได้ที่นี่: https://github.com/jart/cosmopolitan/blob/master/libc/sysv/c...
      ทุกครั้งที่ใช้ค่าคงที่เหล่านี้ จะต้องฝังค่าไว้จำนวนมากในไบนารีเป็นตาราง lookup ขนาดใหญ่ และทุกครั้งที่โปรแกรมต้องตรวจสอบก็ต้องวิ่งผ่านตารางนั้น อาจจะไม่ช้ามาก แต่คิดว่าน่าจะรู้สึกได้แน่
      เป้าหมายคือทำให้พอร์ตได้ง่ายโดยไม่ต้องแก้ซอร์สโค้ดของ libc หรือซอฟต์แวร์ที่จะพอร์ตมากนัก และยังได้ไบนารีที่มีประสิทธิภาพใกล้เคียงหรือดีกว่าเดิม ภายใต้ข้อจำกัดพวกนั้น แพตช์ GCC นี้ดูเหมือนจะเป็นวิธีที่ดีที่สุดในการทำให้กระบวนการง่ายขึ้น
      ถ้าได้เจอโค้ดเบสที่ใช้ SIGHUP สำหรับกำหนดค่าเริ่มต้นดัชนีอาร์เรย์มากพอ ฉันก็ตั้งใจจะลองแนวทางที่คุณเสนอแล้ววัดจุดแลกเปลี่ยนดู หรือคุณจะลองเองแล้วมาบอกก็ได้ว่าการมีชุดค่าคงที่แยกต่างหากดีกว่าหรือเปล่า
  • ถ้า “ใช้เวลาไปมากในการกำจัดตัวอย่างโต้แย้งที่ชัดเจน และซอฟต์แวร์ยอดนิยมจำนวนมากบิลด์ได้อย่างราบรื่น” การเผยแพร่ รายชื่อซอฟต์แวร์ที่คอมไพล์สำเร็จ ก็น่าจะช่วยได้มาก อาจมีอยู่แล้วก็ได้

    • สามารถบิลด์ซอฟต์แวร์อย่าง vim, emacs, ninja, bash, git และ gcc ได้ รายชื่อที่ลองบิลด์ด้วยวิธีนี้มีอยู่ที่นี่: https://github.com/ahgamut/superconfigure
      สคริปต์ superconfigure เป็นแค่ตัวห่อครอบ configure script ทั่วไปที่ใช้บิลด์ซอฟต์แวร์ และใส่แฟล็กอย่าง --enable-static ให้
  • สงสัยว่า APE มีจุดประสงค์เชิงใช้งานจริงหรือไม่ มีที่ไหนบ้างที่แจกจ่ายไฟล์ปฏิบัติการเดียวให้รันได้บนหลายระบบปฏิบัติการจริงๆ?

    • Logfile Navigator(https://lnav.org) ใช้ ไฟล์ปฏิบัติการ APE เป็นเอเจนต์สำหรับสื่อสารกับโฮสต์ระยะไกล
      ตัว lnav เองไม่ได้บิลด์เป็น APE แต่เอเจนต์ที่ฝังอยู่ข้างในบิลด์เป็น APE เมื่อผู้ใช้ต้องการอ่านล็อกจากโฮสต์นั้น ก็จะส่งเอเจนต์นั้นไปยังเครื่องปลายทางจากระยะไกล
      แบบนี้ไม่ต้องมีขั้นตอนเพิ่มเพื่อแยกชนิดระบบปฏิบัติการหรือแนบไฟล์ปฏิบัติการหลายเวอร์ชัน มีบทความสั้นที่เกี่ยวข้องอยู่ที่นี่: https://lnav.org/2021/05/03/tailing-remote-files.html
    • นึกถึงโปรแกรมเมอร์ Java/Clojure/Kotlin, โปรแกรมเมอร์ .Net, JS, Python, Ruby, Erlang และโปรแกรมเมอร์ของ ภาษาที่อิง virtual machine ทั้งหมด
      โปรแกรมที่คอมไพล์แบบสแตติกเป็นรากฐานของทุกอย่างก็จริง แต่ในระดับแอปพลิเคชันถือเป็นส่วนน้อย
      APE ตั้งคำถามใหญ่ไว้ว่า “ถ้าภาษาแบบ AOT ระดับล่างอย่าง C คอมไพล์ครั้งเดียวแล้วรันได้ทุกที่ แล้วใครยังต้องการ virtual machine อีก?”
    • ตัวอย่างเช่น ในที่ที่ยุ่งยากต่อการวางหลายอาร์ติแฟกต์ภายใต้ id/version ชุดเดียวกันอย่าง maven repository การแจกจ่ายไฟล์ปฏิบัติการน่าจะง่ายขึ้นนิดหน่อย PyPI ก็น่าจะมีปัญหาคล้ายกัน
      นอกจากนี้ cross-compilation ก็เป็นงานชวนปวดหัว โปรเจกต์โอเพนซอร์สเล็กๆ ที่ไม่มี build farm พร้อม BSD หลายตัว มักจะไม่แจกไบนารีสำหรับแพลตฟอร์มพวกนั้นเลย
      แม้แต่ kubectl ก็มีไบนารีสำหรับ Linux แต่ไม่มีสำหรับ BSD เว้นแต่ว่าจะนับ OSX เป็น BSD ดังนั้นสำหรับโปรเจกต์แบบนี้มันดูเป็นประโยชน์ที่ได้มาง่ายๆ
    • โปรเจกต์ข้างหน้าของผมจะปล่อยออกมาแบบนั้น และยังบิลด์ ไฟล์ปฏิบัติการ Redbean ให้กับ SPA ฝั่งไคลเอนต์ล้วนด้วย แนวคิดที่ว่าสิ่งเหล่านี้ทำงานได้เลยบนหลายระบบนี่น่าทึ่งมาก
    • มี shader compiler ที่จะได้ประโยชน์หากมีฟอร์แมตไฟล์ปฏิบัติการที่ไม่ผูกกับแพลตฟอร์มและรันได้ทันที
      มันใหญ่เกินกว่าจะบิลด์สดๆ บนเครื่องผู้ใช้ เพราะมี C++ dependency ภายนอกขนาดใหญ่หลายตัว และใช้เวลาบิลด์ตั้งแต่ 2 ถึง 20 นาทีตามฮาร์ดแวร์
      มันต้องรันได้บน macOS x86-64+ARM, Linux x86-64, Windows x86-64 และถ้า ARM Linux ได้ด้วยก็ดี
      ตอนนี้กำลังพิจารณา WASM อยู่ แต่ก็ต้องมี WASI runtime ติดตั้งไว้
  • เผื่อใครยังสับสน: “Actually Portable Executables” https://justine.lol/ape.html

    • ผมเพิ่มคอมเมนต์ที่พูดถึงเรื่องนี้แล้ว แต่ยังไม่เปลี่ยน คิดว่าเดี๋ยวก็คงเปลี่ยน เรียก @dang ตรงนี้ได้ไหมนะ?
  • การเปลี่ยน switch เป็น if ดูค่อนข้างหยาบ อาจแก้ได้ด้วยการแมปไปยังชุดค่าคงที่ที่เป็นกลางต่อแพลตฟอร์ม แล้ว dereference สัญลักษณ์รันไทม์ผ่านฟังก์ชัน
    เช่น ใช้ switch(CosmoMappErrno(EINVAL)) กับ case COSMO_EINVAL: แบบนี้ก็ไม่ต้องทำให้โค้ดกลายเป็น ก้อน goto

    • แต่แบบนั้นก็ไม่ได้ช่วยเรื่องคอมไพล์โค้ดที่ไม่ได้แก้ไขอยู่ดี ไม่ต้องกังวลกับ goto ที่คอมไพเลอร์สร้างขึ้นภายใน คอมไพเลอร์ก็ทำแบบนั้นอยู่แล้วในหลายจุด
      แน่นอน ถ้าหมายถึงให้คอมไพเลอร์รู้จัก switch แบบนี้ แล้วแทนที่จะเขียนกลับเป็นต้นไม้ if ก็เปลี่ยน label เป็นค่าคงที่พิเศษฝั่ง cosmo สำหรับแต่ละค่า และห่อค่าป้อนเข้า switch ด้วยการเรียกฟังก์ชันที่แมปค่าจากแพลตฟอร์มรันไทม์ปัจจุบันไปยังค่าคงที่ฝั่ง cosmo โดยให้ค่าอื่นผ่านไปโดยไม่เปลี่ยน แบบนั้นก็อีกเรื่อง
      อันที่จริงนั่นอาจเป็น การแปลงในคอมไพเลอร์ ที่ง่ายกว่าด้วยซ้ำ เพียงแต่จะมีความซับซ้อนเพิ่มขึ้นจากการต้องรู้ว่ากำลังใช้ชุดค่าคงที่ใดอยู่ และต้องใส่การเรียกแมปที่เหมาะสมกับค่าป้อนเข้า switch ฝั่งไลบรารีก็ต้องสร้างแมปพวกนี้ด้วย จึงต้องรองรับความซับซ้อนดังกล่าว
      ตอนนี้มี implementation ที่ทำงานได้ด้วยวิธีแปลงแบบอื่นอยู่แล้ว จึงไม่แน่ใจว่าผู้เขียนจะอยากลงมือทำแบบนี้ไหม
      สุดท้าย วิธีแมปแบบนี้ก็เสี่ยงแมปผิดได้ และยังมีความจุกจิกอย่างโค้ดที่ใช้ค่า errno ติดลบซึ่งต้องกลับเครื่องหมายของอินพุตก่อนแมป
    • วิธี dereference สัญลักษณ์รันไทม์ผ่านฟังก์ชันฟังดูน่าสนใจ ถ้าแสดงตัวอย่างที่ใช้งานได้ ผมก็ยินดีลอง
      เหตุผลที่ผมชอบอาเรย์ if-else-goto คือมันเข้ากันได้พอดีกับส่วนอื่นๆ ของ GCC ถ้าดูแพตช์ของผม[1] จะเห็นว่าการเพิ่มฟีเจอร์นี้แทบแก้โค้ดเดิมของ GCC นิดเดียว
      [1]: https://github.com/ahgamut/gcc/tree/portcosmo-11.2
    • จำเป็นต้องกังวลถึงขั้นว่าการแทนที่เกิดขึ้นโดยอัตโนมัติหรือไม่?
  • ไม่เข้าใจว่าทำไมถึงประหลาดใจกับโค้ดที่ใช้ SIGxxx เป็นดัชนีขนาดนั้น หมายเลข signal ถูกนิยามไว้ชัดเจน ค่อนข้างต่อเนื่อง และเป็นที่รู้กันว่าเริ่มจาก 1

    • ก่อนจะลอง busybox ผมเคยบิลด์โค้ดเบสหลายตัว เช่น git, bash, gcc, curl ฯลฯ แต่ไม่เคยเห็นโค้ดแบบนั้น
      แถมข้อผิดพลาดนั้นยังโผล่มาพร้อมกับ compiler error ในแพตช์ของผมที่ใช้เวลาดีบักนาน เลยยิ่งจำได้ว่าแปลกใจ
      เลยสงสัยว่าคุณมองว่าแพตเทิร์นโค้ดแบบนั้นพบได้บ่อยไหม แล้วมีในโค้ดเบสไหนบ้างนอกจาก busybox ถ้ามีตัวอย่างเยอะ ผมอาจจะลองใช้เวลาอัปเดตแพตช์ให้รองรับแพตเทิร์นแบบนั้น
  • ถ้า errno เป็นปัญหาใหญ่ขนาดนั้น ก็ไม่รู้ว่าจะยังหวังให้ C++ standard library และ std::error_code ทำงานได้อย่างไร
    ตรงนั้นไม่มีทางใช้แมโครวิเศษอะไรได้เลย

    • ดูเหมือนปัญหาคือ แมโครวิเศษ นั่นเอง ปกติแมโครจะขยายเป็นค่าคงที่ แต่ตอนนี้ไม่เป็นแบบนั้นแล้ว เลยทำให้บางส่วนพัง
      std::error_code ไม่ใช่ค่าคงที่ จึงน่าจะไม่มีปัญหาแบบเดียวกัน