1 คะแนน โดย GN⁺ 2025-04-14 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • rustc_codegen_clr เป็นโปรเจกต์ที่สร้างโค้ด Rust ไปยัง C และ .NET และกำลังเตรียม การนำเสนอใน Rust Week พร้อมกับการปรับปรุงอัตราการผ่านการทดสอบ
  • อัตราการผ่านการทดสอบ Rust core เพิ่มจาก 92% เมื่อสองเดือนก่อนเป็น 95.9% และการทดสอบที่เหลือประมาณ 65 รายการก็ดูเหมือนมีสาเหตุคล้ายกัน
  • การแก้ไขล่าสุดเน้นที่ intrinsic สำหรับจำนวนเต็ม 128 บิต, checked arithmetic และ subslicing ขณะที่ฝั่ง .NET ก็สามารถรันการทดสอบ Rust core ได้ 96.3% แล้ว
  • มีเป้าหมายให้ output ใกล้เคียง C99 หรือ ANSI C และใช้ API มาตรฐาน POSIX เพื่อรองรับ C compiler ให้มากขึ้น โดยใน ANSI C compiler บางตัวสามารถรันโปรแกรม Rust ที่เรียบง่ายมากได้สำเร็จ
  • กำลังทำทั้ง optimization เพื่อลดขนาดไฟล์เมื่อสร้างโค้ด C ขนาดใหญ่ และการ refactor IR ภายใน ซึ่งเป็นพื้นฐานในการขยายขอบเขตการรัน Rust ไปยังแพลตฟอร์มเก่าหรือเฉพาะทาง

การนำเสนอใน Rust Week และสถานะความคืบหน้าของโปรเจกต์

  • มีกำหนดนำเสนอโปรเจกต์ rustc_codegen_clr ในงาน Rust Week ที่เมือง Utrecht ประเทศเนเธอร์แลนด์
  • การนำเสนอพยายามรักษาสมดุลระหว่างการทำให้ผู้เริ่มต้นเข้าถึงได้ กับการครอบคลุมหัวข้อขั้นสูงอย่างการคอมไพล์ Rust-to-C
  • ในช่วงหลายเดือนที่ผ่านมา มีการทำทั้งการแก้ไขการทดสอบ, compatibility กับ C compiler, การปรับปรุง performance และการ refactor ภายใน

อัตราการผ่านการทดสอบ Rust core เพิ่มขึ้น

  • อัตราการผ่านการทดสอบ Rust core เพิ่มจาก 92% เมื่อสองเดือนก่อนเป็น 95.9%
  • ยังเหลือการทดสอบประมาณ 65 รายการ แต่สาเหตุดูคล้ายกัน ทำให้ลักษณะของงานแก้ไขที่เหลือค่อนข้างแคบลงแล้ว
  • ฝั่ง .NET ก็ได้รับผลจากการแก้ไขเดียวกัน และสามารถรันการทดสอบ Rust core ได้ 96.3%

การแก้ไขจำนวนเต็ม 128 บิตและ checked arithmetic

  • การปรับปรุงส่วนใหญ่หลายรายการมาจากการแก้ไข intrinsic 128 บิต, checked arithmetic และ subslicing
  • intrinsic popcount ของ C มีอยู่สามแบบคือ __builtin_popcount, __builtin_popcountl, __builtin_popcountll
    • __builtin_popcountll แม้ชื่อจะเป็นเช่นนั้น แต่ทำงานกับ unsigned long long ไม่ใช่ __int128_t
    • บน x86_64 Linux และ GCC นั้น unsigned long และ unsigned long long ต่างก็มีขนาด 64 บิต
  • implementation เดิมตัด จำนวนเต็ม 128 บิต ใน intrinsic สำหรับ bit counting ให้เหลือ 64 บิตอย่างเงียบ ๆ ทำให้ได้ผลลัพธ์ผิด
  • popcount 128 บิตถูก emulate โดยคำนวณจำนวน set bit ของ 64 บิตล่างและ 64 บิตบนแยกกัน แล้วนำมาบวกกัน
  • overflow check ของการคูณ 128 บิตยังหาวิธีที่มีประสิทธิภาพไม่ได้ จึงจัดการด้วยวิธีง่าย ๆ
    • ถ้า b ไม่ใช่ 0 และ (a * b) / b == a ก็ถือว่าไม่มี overflow
    • ไม่ใช่วิธีที่ก้าวกระโดด แต่เพียงพอให้ผ่านการทดสอบเพิ่มได้อีกหลายรายการ

บั๊ก subslicing และ fallback intrinsic

  • บั๊ก subslicing เกิดจากการขาด sizeof ทำให้ data pointer ของ slice ถูก offset เป็นหน่วยไบต์ ไม่ใช่ หน่วย element
  • บั๊กนี้จะพังเฉพาะตอน subslicing จากด้านท้าย ไม่ใช่จากจุดเริ่มต้นของ slice และเป็นรูปแบบที่มักใช้ใน pattern matching จึงไม่ถูกพบอยู่นาน
  • สำหรับ byte slice และ string slice นั้น ขนาดของ byte และ UTF-8 code unit คือ 1 ไบต์ ทำให้ผ่านการทดสอบของตัวเอง และเพิ่งปรากฏใน test suite ทั้งชุดของ Rust compiler
  • intrinsic บางตัวสามารถใช้ fallback implementation ของ Rust compiler ได้โดยไม่ต้อง implement เองโดยตรง
  • carrying_mul_add ต้องทำการคูณในจำนวนเต็มที่ใหญ่กว่า input 2 เท่า
    • รองรับได้ถึง 64 บิต แต่ input 128 บิตต้องใช้จำนวนเต็ม 256 บิต
    • LLVM รองรับจำนวนเต็ม 256 บิต แต่ C และ .NET ไม่รองรับ
  • fallback implementation ของ Rust compiler ทำการคูณและบวกแบบ 256 บิตด้วยจำนวนเต็ม 128 บิต จึงใช้เป็นแนวทางในการ emulate การคำนวณ 128 บิตด้วยจำนวนเต็ม 64 บิตเท่านั้นได้

รองรับ C compiler ให้มากขึ้น

  • โปรเจกต์นี้พยายามเพิ่มโอกาสให้โค้ด Rust รันบน C compiler เฉพาะทาง ได้มากขึ้น
  • เคยมีกรณีที่โค้ด Rust รันบน Game Boy, คอมไพล์ด้วยคำสั่ง move เท่านั้น หรือรันด้วย Holly C ของ Temple OS
  • ไม่สามารถรองรับ proprietary C compiler ที่เข้าถึงไม่ได้โดยตรง แต่หากขยายขอบเขตการรองรับ obscure C compiler ก็จะเพิ่มโอกาสให้โค้ด Rust รันในสภาพแวดล้อมเหล่านั้นได้
  • หลายแพลตฟอร์มไม่ได้รับการรองรับเพราะขาดเอกสารและเข้าถึงได้ยาก
  • ในประเด็นการหารือเรื่องการเขียนบางส่วนของ Git ด้วย Rust แพลตฟอร์ม proprietary อย่าง NonStop ไม่รองรับ Rust, LLVM หรือ GCC จึงอาจเกิดปัญหา Git รองรับลดลงหรือยุติการรองรับ
  • หากคอมไพล์ Rust เป็น C ในทางทฤษฎีก็สามารถรัน Rust ได้ทุกที่ที่มี C
    • ยังไม่แน่ชัดว่าจะเลี่ยงปัญหาได้ในทุกแพลตฟอร์มหรือไม่
    • การจะได้ compiler สำหรับแพลตฟอร์มนั้นอย่างถูกกฎหมายจำเป็นต้องซื้อเซิร์ฟเวอร์ ซึ่งเกินงบประมาณไปมาก
    • ดูเหมือนว่า Rust จะยังไม่น่ารันได้บนแพลตฟอร์มเหล่านี้ในเร็ว ๆ นี้

กลยุทธ์ output เป็น C ในปัจจุบัน

  • แผนปัจจุบันคือสร้างโค้ดที่เป็น C99 ที่เป็นไปตามมาตรฐาน ให้มากที่สุด หรือใกล้เคียง ANSI C
  • เนื่องจากต้องใช้ threading support บางส่วนสำหรับการ initialize แบบ thread-local จึงกำหนดทิศทางให้ใช้เฉพาะ POSIX API มาตรฐาน
  • มี fallback implementation สำหรับ intrinsic บางตัวเอง และกำลังค่อย ๆ เพิ่มรายการเหล่านั้น
  • ใน ANSI C compiler บางตัว สามารถรันโปรแกรม Rust ที่เรียบง่ายมากได้สำเร็จ
  • เป้าหมายคือให้แม้แพลตฟอร์มที่ตอนนี้ยังรองรับได้ยาก ก็สามารถเพิ่มเข้ามาได้ค่อนข้างง่ายเมื่อเกิดความจำเป็น

การปรับปรุง performance และขนาดไฟล์เล็ก ๆ น้อย ๆ

  • ในการ output integer literal มีการสะท้อนข้อเท็จจริงว่าจำนวนเต็มที่เล็กกว่า 2^32 นั้น decimal จะมีความยาวเท่ากับหรือสั้นกว่า hexadecimal
    • 255 สั้นกว่า 0xFF 1 ไบต์
    • 65536 ก็สั้นกว่า 0xFFFF
    • เพราะ prefix 0x ทำให้ก่อนถึง 2^32 การแสดงผลแบบ hexadecimal จะยังไม่เล็กกว่า
  • ในกรณีสุดโต่งอย่างการแปลง Rust compiler ทั้งหมดเป็น C เคยสร้าง C source file ขนาด สูงสุด 1GB ดังนั้นการลดขนาดไฟล์แม้เพียงสัดส่วนเล็กน้อยก็มีผล
  • directive #line ที่ใช้แทรก debug info ก็ถูกปรับให้ฉลาดขึ้น
    • ใส่ source file name เฉพาะเมื่อมีการเปลี่ยนเท่านั้น
    • สามารถลดขนาดไฟล์ได้มากเมื่อใช้ debug info

การ refactor ภายในและการจัดระเบียบ IR

  • แยกบางฟีเจอร์ภายใน rustc_codegen_clr ออกเป็น crate แยกต่างหาก เพื่อเพิ่มความเร็วของ incremental build
  • กำลังย้ายไปใช้ interned IR ที่มีประสิทธิภาพด้าน memory มากขึ้นด้วย
  • IR เดิมมี rvalue/lvalue แปลก ๆ ที่ map ไปยัง C ได้ไม่ดี และปัญหาจะใหญ่ขึ้นกับฟีเจอร์ของ Rust อย่าง dynamically sized type
  • ตัวอย่างเช่น ใน custom dynamically sized type อย่าง MyStr นั้น &self.s ดูเหมือนเรียบง่าย แต่จริง ๆ ต้องจัดการ fat pointer metadata
    • หากใช้ compound literal ของ C99 ก็สามารถสร้าง struct FatPtr_str ได้ในบรรทัดเดียว
    • ใน ANSI C ต้องใส่ data และ meta ลงใน temporary variable แยกกันแล้ว return
  • เมื่อหนึ่งบรรทัดของ Rust และ MIR ถูกขยายเป็น C หลายบรรทัด old IR จัดการด้วยวิธี internal scope ที่มี temporary local และ sub-statement
  • วิธีใหม่มีขั้นตอน setup ที่ซับซ้อนกว่า แต่ทำให้ IR ทั้งหมดเรียบง่ายขึ้น และหากแก้กรณียากสุดท้ายที่เหลือได้ ก็จะสามารถลบฟีเจอร์ส่วนนั้นของ old IR ได้

งานถัดไป

  • ระยะเวลาทำงานของโปรเจกต์อยู่ที่ประมาณ 1.5 ปี แล้ว และเมื่อบั๊กลดลง การค้นหาบั๊กที่เหลือก็ต้องใช้เวลามากขึ้น
  • กำลังทำบทความ “Rust panics under the hood” ตอนที่ 2 และพยายามอธิบายกระบวนการ panic ของ Rust ทีละขั้น
  • แค่คำอธิบายการสร้าง panic message ก็มีความยาว 10 นาทีแล้ว จึงกำลังพิจารณาแบ่งบทความออกเป็นสองส่วน
  • กำลังทำ memory profiler ขนาดเล็กและแม่นยำสำหรับ Rust ด้วย
    • ขนาดโค้ดประมาณ 2K LOC
    • แม้ตารางเวลาจะค่อนข้างแน่น แต่ก็อยากเขียนบทความเกี่ยวกับเรื่องนี้ภายในไม่กี่สัปดาห์

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

 
GN⁺ 2025-04-14
ความคิดเห็นบน Hacker News
  • ยังรอให้ฟรอนต์เอนด์หรือแบ็กเอนด์ทางเลือกของ Rust สักตัวหนึ่งช่วยให้บูตสแตรป Rust บน alpha, hppa, m68k, sh4 ได้อยู่
    เดิมทีโปรเจกต์ rustc_codegen_gcc เคยสัญญาเรื่องนี้ไว้ แต่สุดท้ายก็ทำไม่ได้
    • สงสัยว่าใช้ทั้งสี่แพลตฟอร์มนั้นจริง ๆ หรือเป็นเกณฑ์ที่ตั้งขึ้นเองเพื่อถือว่าเป็นการรองรับแพลตฟอร์มอย่างครบถ้วน
    • m68k-unknown-linux-gnu ไม่ได้ถูก merge เข้าเป็น target Tier-3 ของ Rust แล้วหรือ? [0]
      [0] https://github.com/rust-lang/compiler-team/issues/458
    • เป้าหมายนั้นถูกยกเลิกไปแล้วหรือ? ครั้งล่าสุดที่ได้ยินมายังอยู่ระหว่างพัฒนาอยู่
    • Rust ยังรองรับ OpenBSD on x86_64 ได้ไม่ดีนักด้วยซ้ำ
  • fractalfir เป็นคนที่มีพรสวรรค์จริง ๆ เห็นบ่อยใน Rust Reddit ผมไม่ได้รู้เรื่องคอมไพเลอร์มากนัก แต่ดูเหมือนคนอื่น ๆ จะชอบผลงานของเขาพอสมควร
    • เท่าที่รู้ยังค่อนข้างอายุน้อย หวังว่าอนาคตจะสดใส
  • จากข้อความที่ว่า “องค์ประกอบส่วนใหญ่ของ std ทำงานได้ราว 95% บน .NET และ 80% บน C” ชื่อหัวข้อบน HN ตกหล่นประเด็นที่ว่า อัตราผ่าน 95% ใช้กับ .NET เท่านั้น
    ฝั่ง GCC/Clang “มีแค่” 80%
    • README ล้าสมัยไปแล้ว ตัวเลขนั้นเป็นของช่วงต้นปีนี้ และตอนนี้เป็นดังนี้
      | .NET Core tests | 1764 | 48 | 20 | 96.29% |
      | C Core tests | 1712 | 71 | 8 | 95.59% |
  • มีการใช้งานน่าสนใจหลายอย่างที่ทำได้ด้วยสิ่งนี้ สิ่งแรกที่นึกถึงคือการปรับปรุง interoperability กับภาษาอื่นอย่าง Python
    • ถ้าใช้ PyO3 interoperability ก็ดีมากอยู่แล้ว ยกเว้นกรณีที่คนพยายาม build ส่วนที่เป็น Rust จากซอร์สแล้วรู้สึกรำคาญที่ต้องติดตั้งคอมไพเลอร์ Rust
      แฮ็กนี้เป็นแบ็กเอนด์ของคอมไพเลอร์ Rust แบ็กเอนด์รับคำสั่งเฉพาะแพลตฟอร์มเป็นอินพุต ดังนั้นโค้ด C ที่สร้างออกมาและไม่ใช่โค้ดแบบเรียบง่ายจะไม่สามารถพกพาข้ามแพลตฟอร์มได้
      ผู้ใช้ต้องได้รับซอร์สที่สร้างไว้ล่วงหน้าเฉพาะแพลตฟอร์ม หรือไม่ก็ติดตั้งคอมไพเลอร์ Rust พร้อมแบ็กเอนด์นี้เพื่อสร้างเอง
    • สิ่งนี้ให้ข้อดีใหม่อะไรที่ฟังก์ชัน extern "c" ของ Rust ทำไม่ได้อยู่แล้ว?
  • แปลงจาก LLVM IR ไป C หรือเปล่า? หรือจาก Rust AST ไป C?
    • พบคำตอบใน README ของโปรเจกต์แล้ว
      “การแทนค่า .NET IR ของผมแมปกับ C ได้ดี และด้วยเหตุนี้จึงสามารถเพิ่มการรองรับการคอมไพล์ Rust เป็น C ได้ด้วยโค้ด 2–3 พันบรรทัด โดยนำ codebase เกือบทั้งหมดกลับมาใช้ซ้ำ และโค้ดเฉพาะสำหรับ C กับ .NET มีอยู่แค่ในขั้นตอนสุดท้ายสุดของการคอมไพล์เท่านั้น”
    • เป็นแบ็กเอนด์ของ rustc หรือก็คือเป็นทางเลือกแทนแบ็กเอนด์ LLVM, GCC, Cranelift
      ตอนแรกเริ่มจากแบ็กเอนด์ .NET แต่พบว่าวิธีเข้าหานี้รองรับการสร้างโค้ด C ได้ง่ายด้วย เลยเพิ่มเข้ามา มันแปลงสิ่งที่ rustc ส่งมาให้เป็น intermediate representation (IR) ของตัวเองแล้วประมวลผลต่อ
  • คอมไพเลอร์จาก Nim ไป C มีอัตราผ่านการทดสอบ 100%
  • แล้วมันยังคง การรับประกัน แบบ Rust ไว้ไหม?
    • ถ้าตัวแปลงเองไม่มีบั๊ก ก็ไม่มีเหตุผลที่ทำไม่ได้ การรับประกันแบบ static ถูกตรวจสอบและมีอยู่แล้ว ภายใต้สมมติฐานว่ากำลังแปลงโค้ด Rust ที่คอมไพล์ได้ด้วยคอมไพเลอร์ Rust ปกติ
      การรับประกันแบบ dynamic อย่างการตรวจสอบขอบเขตก็สามารถ implement ใน C runtime ได้ไม่มีปัญหา
    • มีเหตุผลอะไรที่ทำไม่ได้หรือ?
  • เจ๋งมาก ฝั่งที่แปลง C เป็น Rust ก็น่าจะยอดเยี่ยมเหมือนกัน
    • ถ้าเป็นโปรแกรมที่ไม่ใช่เรื่องง่าย ๆ คงต้องเอาทั้งหมดใส่ไว้ในบล็อก unsafe ขนาดใหญ่บล็อกเดียว
      C ไม่ได้ส่งผ่านข้อมูลทั้งหมดที่จำเป็นต้องรู้อย่างชัดเจนเพื่อให้คอมไพล์ได้ใน Rust
    • Mark Russinovich เพิ่งพูดถึงความพยายาม แปลง C→Rust ขนาดใหญ่ภายใน Microsoft ในงานบรรยายที่งานประชุม Rust ในอังกฤษ
      https://www.youtube.com/watch?v=1VgptLwP588
    • เครื่องมือแบบนั้นมีอยู่แล้ว ปัญหาคือใช้บล็อก unsafe เยอะ และโดยทั่วไปโค้ดที่ได้ไม่ค่อยเป็น Rust สักเท่าไร
      ตัวอย่างเช่น การเปลี่ยน state machine ที่มีสถานะอิงกับตัวแปร global ให้เป็น state machine ที่ดูเป็น Rust มากขึ้น เช่นใช้ enum ที่มีชื่อ เป็นเรื่องยากมาก
      ถ้าได้ AI ที่ทรงพลังพอช่วยอาจเป็นไปได้ แต่ AI ตอนนี้ยังขาดความสามารถในการทำสิ่งที่ตั้งใจไว้ให้สำเร็จจริง ๆ อยู่มาก จึงยังพูดได้ยากว่าพร้อมแล้ว และคงต้องมีหน่วยความจำมากพอจะใส่ codebase ทั้ง C และ Rust ลงใน context window ได้ ซึ่งพอโค้ดเกินระดับหนึ่งก็จะต้องใช้ฮาร์ดแวร์ราคาแพงมากอย่างรวดเร็ว ไม่เช่นนั้นก็จะสร้างโค้ดแยกกันที่เข้ากันไม่ได้ เหมือน LLM ช่วยเขียนโค้ดจำนวนมาก
      อย่างไรก็ดี ถ้าต้องการขยายโปรเจกต์ C ด้วย Rust หรือค่อย ๆ เขียนใหม่ทีละส่วน https://c2rust.com/ ก็ใช้ได้ทันที
    • ดู https://github.com/immunant/c2rust ได้
    • คาดหวังว่าจะได้ประโยชน์อะไรจากสิ่งนี้?
  • Rust เป็น C? จะทำไปทำไม แค่ใช้ C ก็พอแล้ว ถ้าเข้าใจ Rust ได้ ก็น่าจะเข้าใจและใช้ C ได้คล่องแน่นอน
    • เพื่อให้ได้ทั้งข้อดีของ Rust และ ความเข้ากันได้กับคอมไพเลอร์ gcc/C รวมถึงการรองรับสถาปัตยกรรม ที่กว้างขวาง
      Rust เป็นภาษาสมัยใหม่ที่มี package management, เครื่องมือ build/test แบบบูรณาการที่เรียบง่ายขึ้น, ภาระเก่า ๆ น้อยกว่ามาก, และมีฟีเจอร์กับไวยากรณ์ระดับสูงที่ผู้คนชอบใช้จริง ๆ
      C ก็เรียบง่ายดี แต่ codebase ที่ซับซ้อนได้ประโยชน์จากภาษาสมัยใหม่ที่ช่วยสร้าง abstraction ที่แข็งแรงโดยยังคงความเร็วของ C ไว้ได้ แน่นอนว่ายังไม่ต้องพูดถึง borrow checker และความปลอดภัยของหน่วยความจำ
    • เพื่อให้ได้ข้อดีของ Rust บนแพลตฟอร์มที่ rustc ไม่รองรับ ค่อนข้างตรงไปตรงมา
    • ตรงนี้ดูจะมีลักษณะ “ทำเพราะทำได้” อยู่ไม่น้อย แต่ก็เพราะยังมีแพลตฟอร์มจำนวนมากที่ Rust ไม่รองรับด้วย
      ถ้ามีตัวแปลง Rust→C ที่สร้างโค้ด C ได้มาตรฐานพอ ก็จะช่วยอุดช่องว่างนั้นได้