การเรนเดอร์th-TH

Compiling and targets

Codegen targets#

Faber มีภาษาเดียวและมีสัญญาการคอมไพล์หลายรูปแบบ ฟีเจอร์แต่ละอย่างไม่จำเป็นต้องลดรูปไปยังทุกเป้าหมายได้ หน้านี้อธิบายว่าแต่ละเป้าหมายรองรับ ลบออก แจ้งเตือน หรือปฏิเสธฟีเจอร์ใดบ้าง

คำกริยาตามนโยบาย#

คำกริยาความหมาย
รองรับลดรูปด้วยความหมายตามที่กำหนด
ลบออกตรวจสอบชนิดข้อมูลได้ แต่การสร้างโค้ดจะละทิ้งความหมายเฉพาะของเป้าหมาย
เตือนเป็น Faber ที่ถูกต้องตามกฎหมาย แต่ไม่มีผลหรือมีพฤติกรรมลดทอนบนเป้าหมาย
ปฏิเสธการตรวจสอบหรือการส่งออกล้มเหลวพร้อมข้อความวินิจฉัยที่ชัดเจน
เลื่อนดำเนินการแยกวิเคราะห์และผูกชื่อได้ แต่ยังไม่ได้ทำการลดรูปสำหรับเป้าหมายใด
จำกัดสัญญาที่เสถียรพร้อมเงื่อนไขขอบเขตย่อยที่ชัดเจน

ตารางเป้าหมาย#

เป้าหมายเลนสร้างรันแพ็กเกจนโยบาย
rustHIRใช่ใช่ใช่รองรับ
fmir-textMIRใช่ใช่ใช่รองรับ
fmirMIRใช่ใช่ใช่รองรับ
fmir-binMIRใช่ใช่ใช่รองรับ
faberHIRใช่ไม่ไม่รองรับ
tsHIRใช่ไม่ไม่ตรวจสอบ
goHIRใช่ไม่ไม่ลบออก
wasmMIRใช่ไม่ไม่จำกัด
wasm-textMIRใช่ไม่ไม่จำกัด
llvm-textMIRใช่ไม่ไม่จำกัด
metal-textMIRใช่ไม่ไม่จำกัด
wgsl-textMIRใช่ไม่ไม่จำกัด
sexpMIRใช่ไม่ไม่จำกัด

การกำหนดเส้นทางไปป์ไลน์#

Source → Lex → Parse → Collect → Resolve → Lower → Typecheck → Analysis
                                                              ↓
                                    ┌─────────────────────────┴──────────┐
                                    │                                    │
                              HIR backends                        MIR backends
                                    │                                    │
            Rust · Faber · TS · Go            fmir · wasm · llvm · metal · wgsl · sexp

เลนแอปพลิเคชัน (HIR)#

เป้าหมายระดับพื้นฐานที่วัดได้
Rustเส้นทางสำหรับใช้งานจริง รองรับโหมดยืม การสร้าง CLI และการลดรูป Result ที่อาจล้มเหลว
Faberมุมมองซอร์สตามรูปแบบมาตรฐาน / การแปลงไปกลับ ไม่ใช่แบ็กเอนด์สำหรับการประมวลผล
TypeScriptวิเคราะห์แล้ว 288/318 รายการ · ตรวจสอบชนิดข้อมูลผ่าน 268/318 รายการ · รันได้ 262/318 รายการ
Goผ่าน 146/216 รายการ โหมดยืมถูกลบออก และ ad ถูกปฏิเสธ

เลนระบบ (MIR)#

เป้าหมายระดับพื้นฐานที่วัดได้
fmir*อิมเมจ MIR ของแพ็กเกจ ตัวรันพิสูจน์ว่าไม่ขึ้นกับซอร์ส
wasmส่งออกแล้ว 200/289 รายการ · ตรวจสอบความถูกต้องผ่าน 195/289 รายการ · รันด้วย stub host ได้ 171/289 รายการ
llvm-textส่งออกแล้ว 249/289 รายการ · ผ่านการตรวจสอบ 232/289 รายการ · รันได้ 65/289 รายการ
metal-textชุดย่อยของเคอร์เนลที่ปลอดภัยสำหรับอุปกรณ์ มีการทดสอบเฉพาะจุด 88 รายการ แคมเปญหยุดชั่วคราว
wgsl-textตรวจสอบด้วย naga 30.x มีการทดสอบเฉพาะจุด 87 รายการ และมี sidecar สำหรับการสะท้อนข้อมูล
sexpส่งออกแล้ว 193 รายการ · คอมไพล์ด้วย Racket แล้ว 190 รายการ · รันด้วย Racket แล้ว 190 รายการ เป้าหมายสำหรับการตรวจสอบความถูกต้อง

สำหรับแฟล็กความสามารถล่าสุด ให้รัน faber targets

Compilation lanes#

Faber มีฟรอนต์เอนด์ร่วมเพียงชุดเดียว ได้แก่ การวิเคราะห์คำศัพท์ การแยกวิเคราะห์ และการตรวจสอบชนิด จากนั้นจึงแยกไปยังเส้นทางการลดรูปหลายรูปแบบตามความต้องการของเป้าหมาย ตัวแทนระดับกลางก่อตัวเป็นไปป์ไลน์: ซอร์สโค้ดถูกวิเคราะห์เป็น HIR จากนั้นอาจลดรูปเป็น MIR และอาจแวะผ่าน AIR ก่อนส่งออกขั้นสุดท้าย IR แต่ละแบบมีหน้าที่เฉพาะ และแต่ละเป้าหมายจะรับอินพุตจาก IR ที่เหมาะกับความต้องการของตน

ภาพรวมไปป์ไลน์#

Source (.fab)  →  Lex  →  Parse  →  Collect  →  Resolve  →  Lower  →  Typecheck  →  Analysis
                                                              │
                                                    HIR (semantic core)
                                                    ┌─────┴─────┐
                                                    │           │
                                              Reader locale    MIR lowering
                                              (input/output)    │
                                                                │
                                                      ┌─────────┴─────────┐
                                                      │                   │
                                                CPU lanes           GPU lanes
                                                      │                   │
                                            ┌────┬────┼────┬────┐     ┌───┴───┐
                                            │    │    │    │    │     │       │
                                          FMIR LLVM WASM  TS  Go   WGSL   Metal
                                                                          (hold)

ฟรอนต์เอนด์ชุดเดียวกันนี้ให้บริการทุกเป้าหมาย หลังจากการวิเคราะห์เชิงความหมายสร้าง HIR แล้ว คอมไพเลอร์จะเลือกเส้นทางตามเป้าหมาย:

  • HIR-direct — ส่งออกโดยตรงจาก HIR ที่ตรวจสอบชนิดแล้ว สำหรับแบ็กเอนด์ที่มีรูปแบบใกล้เคียงภาษา (Rust, Faber, TypeScript, Go)
  • HIR → MIR — ลดรูปเป็น MIR ที่มีรูปแบบใกล้เคียงการทำงาน จากนั้นจึงส่งออกสำหรับเป้าหมายระบบและเป้าหมายระดับล่าง
  • HIR → MIR → AIR → MIR — แวะผ่าน AIR แบบฟังก์ชันบริสุทธิ์สำหรับการแปลงออโตดิฟและฟิวชัน จากนั้นจึงกลับเข้าสู่ MIR

HIR — ตัวแทนระดับกลางระดับสูง#

HIR คือแหล่งความจริง เป็น IR ที่มีชนิดและมีรูปแบบใกล้เคียงภาษา ซึ่งยังคงรักษาการประกาศ ข้อมูลชนิด และความสัมพันธ์เชิงโครงสร้างไว้ ทุกโปรแกรม Faber ไม่ว่าจะมาจากโลแคลเดิมหรือมีปลายทางเป็นเป้าหมายใด ล้วนต้องผ่านตัวแทนนี้

การผสานรวมกับโลแคลผู้อ่าน#

โลแคลผู้อ่านทำงานผ่าน HIR ไฟล์ซอร์ส Faber ที่เขียนด้วยคีย์เวิร์ดภาษาไทยจะถูกแยกวิเคราะห์และลดรูปเป็น HIR เดียวกับซอร์สที่เทียบเท่ากันในภาษาละติน โลแคลเป็นการแสดงผลระดับพื้นผิวของ HIR ไม่ใช่การแยกแขนงในแกนเชิงความหมาย

  • อินพุต: ซอร์สที่แปลเป็นภาษาท้องถิ่น (ไทย จีน อาหรับ และอื่น ๆ) → HIR ที่ทำให้เป็นมาตรฐานแล้ว — ใช้งานแล้ว
  • เอาต์พุต: HIR → การส่งออกซอร์สที่แปลเป็นภาษาท้องถิ่นอีกครั้ง — อยู่ระหว่างดำเนินการ (กำลังพัฒนา)

เมื่อทิศทางเอาต์พุตพร้อมใช้งาน faber format --reader-locale=th-TH จะวนรอบซอร์ส Faber ใด ๆ ผ่าน HIR และส่งออกด้วยคีย์เวิร์ดภาษาไทย ทำให้ความสมมาตรเสร็จสมบูรณ์: HIR เดียวกันสามารถสร้างพื้นผิวของโลแคลใด ๆ ได้ เช่นเดียวกับที่สามารถสร้างเอาต์พุตสำหรับแบ็กเอนด์เป้าหมายใด ๆ

แบ็กเอนด์ HIR-direct#

เป้าหมายเหล่านี้ส่งออกโดยตรงจาก HIR ที่ตรวจสอบชนิดแล้ว โดยไม่ลดรูปเป็น MIR จึงรักษาโครงสร้างระดับซอร์สไว้ได้นานกว่า และเหมาะกับเอาต์พุตที่มีรูปแบบใกล้เคียงภาษา:

เป้าหมายสถานะบทบาท
Rustหลักเส้นทางสำหรับการใช้งานจริง แพ็กเกจ การบิลด์ การรัน และการทดสอบ ใช้ Cargo + rustc สำหรับไบนารีเนทีฟ
Faberรองรับมุมมองซอร์สตามรูปแบบมาตรฐานผ่านฟอร์แมตเตอร์ forma พร้อมความเสถียรในการวนรอบ
TypeScriptตรวจสอบส่งออกไฟล์เท่านั้น ใช้พิสูจน์ความหมายข้ามรูปแบบเป้าหมาย
Goลบรูปส่งออกไฟล์เท่านั้น โหมดการยืมจะถูกลบออก และปฏิเสธ ad

MIR — ตัวแทนระดับกลางระดับกลาง#

MIR คือ IR ที่มีรูปแบบใกล้เคียงการทำงาน โดยแสดงโฟลว์การควบคุม ตัวแปรโลคัล การเรียกใช้รันไทม์ ตำแหน่ง สาขา และขอบเขตข้อผิดพลาด ซึ่งเป็นข้อเท็จจริงที่เป้าหมายระดับล่างต้องใช้ ขณะที่ HIR รักษาโครงสร้างของซอร์ส MIR จะทำให้โครงสร้างนั้นแบนลงเป็นกราฟโฟลว์การควบคุม

การลดรูป HIR → MIR จะแปลงโครงสร้างที่มีรูปแบบใกล้เคียงภาษาให้เป็นขั้นตอนการทำงาน MIR จะถูกตรวจสอบหลังการลดรูป เพื่อดักจับปัญหาเชิงโครงสร้างก่อนที่แบ็กเอนด์ใด ๆ จะพยายามส่งออก

ความเป็นเจ้าของเชิงความหมาย Faber รักษาขอบเขตที่ชัดเจนระหว่างกฎที่บังคับใช้ใน HIR/MIR (การตรวจสอบชนิด การกำหนดค่าอย่างแน่นอน และลินต์โหมดการยืม) กับกฎที่ปล่อยให้ชุดเครื่องมือของเป้าหมายจัดการ (การวิเคราะห์อายุการใช้งานของ Rust และความปลอดภัยด้านชนิดของ Go) วิธีนี้ป้องกันไม่ให้คอมไพเลอร์ทำงานซ้ำกับสิ่งที่คอมไพเลอร์ของเป้าหมายทำได้ถูกต้องอยู่แล้ว

การแวะผ่าน AIR#

AIR (Autograd / AI Representation) คือเส้นทางแวะผ่านสำหรับการแปลงแบบฟังก์ชันบริสุทธิ์ ซึ่งแยกออกจากเส้นทาง HIR → MIR การเข้าสู่เส้นทางนี้เกิดจากคำอธิบายประกอบที่ระบุอย่างชัดเจนบนฟังก์ชันแต่ละรายการ:

@ radix lane "air"
functio loss(numerus predicted, numerus expected)  numerus {
    fixum numerus delta  predicted - expected
    redde delta * delta
}

ฟังก์ชันในเลน AIR ต้องเป็นไปตามนโยบายความบริสุทธิ์ — ห้ามมีการกลายสภาพ ห้ามมีเอฟเฟกต์ และห้ามมีลูป ฟังก์ชันที่ละเมิดเงื่อนไขนี้จะถูกปฏิเสธพร้อมข้อความวินิจฉัยก่อนเริ่มการลดรูปเป็น AIR ส่วนที่เหลือของโปรแกรมยังคงใช้ Faber ตามปกติ ซึ่งรองรับการกลายสภาพ เอฟเฟกต์ และลูป

หลังจากการแปลง AIR ทำงานเสร็จ (ในอนาคตจะรองรับออโตดิฟและฟิวชัน) ผลลัพธ์จะถูกลดรูปกลับเป็น MIR และกลับเข้าสู่ไปป์ไลน์แบ็กเอนด์ MIR ตามปกติ AIR ไม่มีแบ็กเอนด์ของตนเองและไม่มีตัวตรวจสอบชนิดอิสระ เพราะ AIR เป็นจุดตรวจสำหรับการแปลง ไม่ใช่ IR แบบขนาน

HIR  →  AIR purity check  →  HIR to AIR lowering  →  AIR validation  →  AIR to MIR re-lowering  →  MIR backend

สถาปัตยกรรมนี้มีแนวทางคล้าย JAX: รักษาตัวแทนแบบฟังก์ชันบริสุทธิ์ไว้สำหรับการแปลง แล้วค่อยลดรูปเป็น IR เชิงคำสั่งเมื่อถึงขั้นสุดท้าย AIR มีอยู่เพราะการทำออโตดิฟบน MIR เชิงคำสั่งจะต้องสร้างความบริสุทธิ์ขึ้นใหม่จากโค้ดที่ถูกลดรูปให้เป็นการกลายสภาพแล้ว

เลนเป้าหมาย CPU#

เป้าหมาย CPU จะรับ MIR และสร้างเป็นอาร์ติแฟกต์ที่เรียกใช้งานได้หรือข้อความสำหรับชุดเครื่องมือภายนอก Faber ส่งออกข้อความเมื่อทำได้ และพึ่งพาชุดเครื่องมือระดับล่างสำหรับขั้นตอนคอมไพล์สุดท้าย ซึ่งคล้ายกับคอมไพเลอร์ C ที่ส่งออกแอสเซมบลีให้แอสเซมเบลอร์และตัวลิงก์

FMIR — รันไทม์ MIR ของ Faber เอง#

FMIR คือเอกซีคิวเตอร์แพ็กเกจแบบเนทีฟต่อ MIR คอมไพเลอร์จะแยก MIR เป็นเพย์โหลดไบนารี แล้วห่อด้วยตัวโหลดเคอร์เนล Rust ขนาดสั้น ผลลัพธ์คือไฟล์ปฏิบัติการในตัวเองที่รัน MIR ผ่านสเต็ปเปอร์ภายในโปรเซสของ Faber โดยไม่ต้องติดตั้งรันไทม์แยกต่างหาก

รูปแบบคำอธิบาย
fmir-textอิมเมจข้อความ FMIR ที่ตรวจสอบได้ที่ target/faber-mir/image.fmir.txt
fmirอิมเมจไบนารี FMIR แบบกระชับที่ target/faber-mir/image.fmir
fmir-binรันเนอร์ในตัวเองที่ target/faber-mir/exe/run ซึ่งฝังไบต์ FMIR ไว้ภายใน

ข้อความ LLVM#

Faber ส่งออก LLVM IR เป็นข้อความ (.ll) ไม่ใช่การสร้างโค้ด LLVM แบบผสานรวม IR ที่ส่งออกมีไว้สำหรับขั้นตอนของชุดเครื่องมือภายนอก โดยเครื่องมือ LLVM ปลายทางจะจัดการการตรวจสอบ การปรับให้เหมาะสม และการสร้างโค้ดเนทีฟ นี่คือเป้าหมายสำหรับการจัดเตรียมและการตรวจสอบ ไม่ใช่เส้นทางการสร้างโค้ดเนทีฟที่ฝังอยู่ในคอมไพเลอร์

WASM#

Faber ส่งออก WebAssembly ในรูปแบบข้อความ (.wat) และไบนารี (.wasm) Wasm ที่ส่งออกใช้การนำเข้าจากโฮสต์ภายนอก (สัญลักษณ์รันไทม์ faber_*) และตรวจสอบผ่าน wasm-tools validate Wasm เป็นเป้าหมายที่รองรับโดยมีข้อจำกัด — ใช้พิสูจน์ไปป์ไลน์การลดรูป MIR สำหรับรูปแบบมาตรฐานแบบเปิด แต่ไม่ใช่รันไทม์สำหรับส่งมอบแพ็กเกจ

รูปแบบเป้าหมาย CLIเอาต์พุต
wasm-text-t wasm-text (ชื่อแทน wat)รูปแบบข้อความ WAT
wasm-t wasmโมดูล Wasm แบบไบนารี

TypeScript และ Go (HIR-direct)#

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

เลนเป้าหมาย GPU#

WGSL (ผ่าน WGPU)#

Faber ส่งออกซอร์ส compute shader ของ WGSL ผ่านไปป์ไลน์ MIR WGSL ที่ส่งออกจะถูกตรวจสอบผ่าน naga (30.x) และมี sidecar สำหรับการสะท้อนข้อมูลเมตาของ bind group ขอบเขตนี้ครอบคลุมเซตย่อยของเคอร์เนลที่ปลอดภัยต่ออุปกรณ์ โดยรองรับ device view แบบ f32 มิติ 1 และปฏิเสธ view มิติ 2 WGSL ไม่ใช่รันไทม์สำหรับเปิดใช้ GPU — Faber ส่งออกซอร์สเชดเดอร์ แต่การทำงานจริงต้องใช้รันไทม์ WebGPU ภายนอก

Metal (พักไว้)#

การส่งออกข้อความ compute shader ของ Metal ได้รับการออกแบบและพัฒนาไปบางส่วนแล้ว แต่ขณะนี้พักไว้ก่อน สถาปัตยกรรมใช้รูปแบบเดียวกับ WGSL: Faber ส่งออกซอร์ส Metal Shading Language สำหรับเซตย่อยของเคอร์เนลที่ปลอดภัยต่ออุปกรณ์ ขณะที่ชุดเครื่องมือภายนอกจัดการการคอมไพล์และการเรียกใช้ มีแผนกลับมาดำเนินงานต่อ

หมายเหตุด้านสถาปัตยกรรม#

สถาปัตยกรรมการคอมไพล์ของ Faber มีแนวคิดคล้ายกับการทำงานของคอมไพเลอร์ Rust Rust ลดรูปผ่าน HIR → MIR → LLVM IR และฝังชุดเครื่องมือ LLVM ไว้โดยตรงสำหรับการสร้างโค้ดเนทีฟขั้นสุดท้าย ส่วน Faber ใช้แนวทางที่ยืดหยุ่นกว่า โดยส่งออกข้อความสำหรับชุดเครื่องมือภายนอก (ข้อความ LLVM, WGSL, Metal และ WAT) แทนการฝังชุดเครื่องมือเหล่านั้น ขณะเดียวกันยังคงสงวนการส่งออกโค้ดโดยตรงไว้สำหรับรันไทม์ของตนเอง (FMIR) และเป้าหมายแพ็กเกจหลักของตน (Rust ซึ่ง Cargo และ rustc จัดการไปป์ไลน์ขั้นถัดไป)

แนวทางการส่งออกเป็นข้อความทำให้ Faber ไม่จำเป็นต้องรวม LLVM รันไทม์ Wasm หรือไดรเวอร์ GPU ไว้ภายใน เพราะสิ่งเหล่านี้ยังคงเป็นดีเพนเดนซีภายนอกที่ผู้ใช้เลือกเอง ข้อแลกเปลี่ยนคือ Faber ไม่สามารถเสนอการบิลด์ด้วยคำสั่งเดียวสำหรับทุกเป้าหมายได้ ผู้ใช้ต้องติดตั้งชุดเครื่องมือที่เหมาะสมสำหรับแบ็กเอนด์ที่เลือก

สรุปเป้าหมาย#

เป้าหมายIRกลุ่มบิลด์รันแพ็กเกจ
RustHIRCPUใช่ใช่ใช่
fmir / fmir-binMIRCPUใช่ใช่ใช่
Faber (format)HIRไม่ไม่ไม่
TypeScriptHIRCPUไม่ไม่ไม่
GoHIRCPUไม่ไม่ไม่
LLVM textMIRCPUไม่ไม่ไม่
WASM / WATMIRCPUไม่ไม่ไม่
WGSLMIRGPUไม่ไม่ไม่
Metal (hold)MIRGPUไม่ไม่ไม่

`build`, `run` และ `package` หมายถึงเวิร์กโฟลว์ของ Faber ชุดเครื่องมือภายนอก (rustc, wasm-tools, naga) จะจัดการการคอมไพล์ขั้นสุดท้ายสำหรับเป้าหมายที่ส่งออกเป็นข้อความ

Compiler performance#

ฟรอนต์เอนด์ของ Radix มีอัตราการขยายโดยประมาณเป็นเส้นตรงตามขนาดซอร์ส โดยทำงานภายในกระบวนการเดียวและใช้เธรดเดียว

เวลาในการคอมไพล์ฟรอนต์เอนด์#

ขนาดโปรแกรมซอร์สเวลาคอมไพล์มัธยฐาน
100 ฟังก์ชัน / ประมาณ 650 บรรทัดประมาณ 10 KBประมาณ 0.6 มิลลิวินาที
500 ฟังก์ชัน / ประมาณ 3.3K บรรทัดประมาณ 52 KBประมาณ 3 มิลลิวินาที
1,000 ฟังก์ชัน / ประมาณ 6.5K บรรทัดประมาณ 105 KBประมาณ 6 มิลลิวินาที
5,000 ฟังก์ชัน / ประมาณ 32K บรรทัดประมาณ 530 KBประมาณ 37 มิลลิวินาที

ตัวอย่างจริงที่ใหญ่ที่สุดในปัจจุบันมีประมาณ 140 บรรทัด ซึ่งต่ำกว่าระดับสัญญาณรบกวนมาก

ต้นทุนของแบ็กเอนด์ (เป้าหมาย Rust)#

สำหรับ faber build เวลาที่ผู้ใช้รับรู้ส่วนใหญ่ถูกใช้ไปกับ Cargo/rustc ไม่ใช่ฟรอนต์เอนด์ของ Faber:

ระยะต้นทุน
คอมไพล์ดีเพนเดนซีของ faber แบบแคชว่าง (ครั้งเดียวต่อตำแหน่งเป้าหมาย)ประมาณ 2.8 วินาที
คอมไพล์ดีเพนเดนซีของ tokio แบบแคชว่าง (เฉพาะเมื่อต้องใช้)ประมาณ 2.3 วินาที
บิลด์ต่อโปรแกรมแบบวอร์ม (ดีเพนเดนซีอยู่ในแคช)ประมาณ 30–110 มิลลิวินาที
โอเวอร์เฮดจากการเรียก Cargo ต่อโปรแกรมประมาณ 400 มิลลิวินาที

การคอมไพล์แบบเพิ่มทีละส่วน#

crate faber-runtime จะคอมไพล์หนึ่งครั้งต่อตำแหน่งเป้าหมาย และแคชเป็นอาร์ติแฟกต์ .rlib:

สิ่งที่คุณเปลี่ยนcrate faber-runtimeโปรแกรมของคุณ
ซอร์สของโปรแกรมใช้แคชคอมไพล์ใหม่
norma/src/*.fab (ซอร์ส Faber)ใช้แคชคอมไพล์ใหม่
faber/runtime/rust/src/*.rsคอมไพล์ใหม่หนึ่งครั้งคอมไพล์ใหม่

กับดักที่ควรหลีกเลี่ยงคือการบิลด์แต่ละโปรแกรมลงใน target/ ใหม่ ให้ใช้ --target-dir ร่วมกัน เพื่อให้ .rlib ที่อยู่ในแคชยังคงพร้อมใช้งาน —