LLVM คืออะไร? พลังที่อยู่เบื้องหลัง Swift, Rust, Clang และอื่น ๆ

ภาษาใหม่และการปรับปรุงภาษาที่มีอยู่กำลังเกิดขึ้นทั่วทั้งภูมิทัศน์ที่ดีงาม Rust ของ Mozilla, Swift ของ Apple, Kotlin ของ Jetbrains และภาษาอื่น ๆ อีกมากมายทำให้นักพัฒนามีตัวเลือกใหม่ ๆ สำหรับความเร็วความปลอดภัยความสะดวกในการพกพาและพลังงาน

ทำไมตอนนี้? เหตุผลสำคัญประการหนึ่งคือเครื่องมือใหม่สำหรับการสร้างภาษาโดยเฉพาะคอมไพเลอร์ และหัวหน้าของพวกเขาคือ LLVM ซึ่งเป็นโครงการโอเพ่นซอร์สที่พัฒนาโดย Chris Lattner ผู้สร้างภาษา Swift ในฐานะโครงการวิจัยที่มหาวิทยาลัยอิลลินอยส์

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

บัญชีรายชื่อภาษาที่ใช้ LLVM มีชื่อที่คุ้นเคยมากมาย ภาษา Swift ของ Apple ใช้ LLVM เป็นเฟรมเวิร์กคอมไพเลอร์และ Rust ใช้ LLVM เป็นส่วนประกอบหลักของห่วงโซ่เครื่องมือ นอกจากนี้คอมไพเลอร์จำนวนมากยังมีรุ่น LLVM เช่น Clang คอมไพเลอร์ C / C ++ (ชื่อนี้คือ“ C-lang”) ซึ่งเป็นโครงการที่เป็นพันธมิตรอย่างใกล้ชิดกับ LLVM Mono การใช้งาน. NET มีตัวเลือกในการคอมไพล์กับโค้ดเนทีฟโดยใช้แบ็คเอนด์ LLVM และ Kotlin ซึ่งมีชื่อเป็นภาษา JVM กำลังพัฒนาเวอร์ชันของภาษาที่เรียกว่า Kotlin Native ซึ่งใช้ LLVM เพื่อคอมไพล์เป็นรหัสของเครื่อง

LLVM กำหนด

หัวใจสำคัญคือ LLVM คือไลบรารีสำหรับการสร้างโค้ดที่เป็นเครื่องโดยใช้โปรแกรม นักพัฒนาใช้ API เพื่อสร้างคำสั่งในรูปแบบที่เรียกว่าการเป็นตัวแทนระดับกลางหรือ IR จากนั้น LLVM สามารถคอมไพล์ IR เป็นไบนารีแบบสแตนด์อโลนหรือทำการคอมไพล์ JIT (just-in-time) บนโค้ดเพื่อรันในบริบทของโปรแกรมอื่นเช่นล่ามหรือรันไทม์สำหรับภาษา

API ของ LLVM มีพื้นฐานสำหรับการพัฒนาโครงสร้างและรูปแบบทั่วไปที่พบในภาษาโปรแกรม ตัวอย่างเช่นเกือบทุกภาษามีแนวคิดเกี่ยวกับฟังก์ชันและตัวแปรส่วนกลางและหลายภาษามีโครูทีนและอินเตอร์เฟสฟังก์ชัน C ต่างประเทศ LLVM มีฟังก์ชันและตัวแปรส่วนกลางเป็นองค์ประกอบมาตรฐานใน IR และมีอุปลักษณ์สำหรับการสร้างโครูทีนและเชื่อมต่อกับไลบรารี C

แทนที่จะใช้เวลาและพลังงานในการคิดค้นล้อใหม่เหล่านั้นคุณสามารถใช้การใช้งานของ LLVM และมุ่งเน้นไปที่ส่วนต่างๆของภาษาที่คุณต้องการได้

อ่านเพิ่มเติมเกี่ยวกับ Go, Kotlin, Python และ Rust 

ไป:

  • แตะพลังของภาษา Go ของ Google
  • IDE และบรรณาธิการภาษา Go ที่ดีที่สุด

Kotlin:

  • Kotlin คืออะไร? ทางเลือก Java อธิบาย
  • Kotlin frameworks: การสำรวจเครื่องมือพัฒนา JVM

Python:

  • Python คืออะไร? ทุกสิ่งที่คุณต้องรู้
  • บทช่วยสอน: วิธีเริ่มต้นกับ Python
  • 6 ไลบรารีที่จำเป็นสำหรับนักพัฒนา Python ทุกคน

สนิม:

  • สนิมคืออะไร? วิธีการพัฒนาซอฟต์แวร์ที่ปลอดภัยรวดเร็วและง่ายดาย
  • เรียนรู้วิธีเริ่มต้นกับ Rust 

LLVM: ออกแบบมาเพื่อการพกพา

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

ในทางตรงกันข้าม IR ของ LLVM ได้รับการออกแบบมาตั้งแต่ต้นให้เป็นชุดประกอบแบบพกพา วิธีหนึ่งที่ทำให้การพกพานี้ประสบความสำเร็จคือการนำเสนอผลิตภัณฑ์ดั้งเดิมที่เป็นอิสระจากสถาปัตยกรรมเครื่องใด ๆ โดยเฉพาะ ตัวอย่างเช่นประเภทจำนวนเต็มไม่ได้ จำกัด อยู่ที่ความกว้างบิตสูงสุดของฮาร์ดแวร์พื้นฐาน (เช่น 32 หรือ 64 บิต) คุณสามารถสร้างประเภทจำนวนเต็มพื้นฐานโดยใช้บิตได้มากเท่าที่ต้องการเช่นจำนวนเต็ม 128 บิต นอกจากนี้คุณยังไม่ต้องกังวลเกี่ยวกับการสร้างเอาต์พุตให้ตรงกับชุดคำสั่งของโปรเซสเซอร์ที่เฉพาะเจาะจง LLVM ดูแลสิ่งนั้นให้คุณด้วย

การออกแบบที่เป็นกลางทางสถาปัตยกรรมของ LLVM ช่วยให้รองรับฮาร์ดแวร์ทุกชนิดทั้งในปัจจุบันและอนาคตได้ง่ายขึ้น ตัวอย่างเช่น IBM เพิ่งสนับสนุนโค้ดเพื่อสนับสนุน z / OS, Linux on Power (รวมถึงการสนับสนุนไลบรารีเวกเตอร์ MASS ของ IBM) และสถาปัตยกรรม AIX สำหรับโครงการ C, C ++ และ Fortran ของ LLVM 

หากคุณต้องการดูตัวอย่างจริงของ LLVM IR ให้ไปที่เว็บไซต์ ELLCC Project และลองใช้การสาธิตสดที่แปลงรหัส C เป็น LLVM IR ในเบราว์เซอร์

ภาษาโปรแกรมใช้ LLVM อย่างไร

กรณีการใช้งานที่พบบ่อยที่สุดสำหรับ LLVM คือคอมไพเลอร์ล่วงหน้า (AOT) สำหรับภาษา ตัวอย่างเช่นโครงการ Clang ล่วงหน้าจะรวบรวม C และ C ++ เป็นไบนารีดั้งเดิม แต่ LLVM ทำให้สิ่งอื่นเป็นไปได้เช่นกัน

การคอมไพล์แบบทันเวลาด้วย LLVM

บางสถานการณ์จำเป็นต้องสร้างโค้ดในทันทีที่รันไทม์แทนที่จะคอมไพล์ล่วงหน้า ตัวอย่างเช่นภาษา Julia JIT คอมไพล์โค้ดเนื่องจากต้องทำงานอย่างรวดเร็วและโต้ตอบกับผู้ใช้ผ่าน REPL (read-eval-print loop) หรือพร้อมต์โต้ตอบ 

Numba แพคเกจเร่งความเร็วทางคณิตศาสตร์สำหรับ Python JIT รวบรวมฟังก์ชัน Python ที่เลือกไว้กับรหัสเครื่อง นอกจากนี้ยังสามารถรวบรวมโค้ดที่ตกแต่งด้วย Numba ล่วงหน้าได้ แต่ Python (เช่น Julia) มีการพัฒนาอย่างรวดเร็วโดยเป็นภาษาที่ตีความได้ การใช้การคอมไพล์ JIT เพื่อสร้างโค้ดดังกล่าวช่วยเติมเต็มเวิร์กโฟลว์เชิงโต้ตอบของ Python ได้ดีกว่าการคอมไพล์ล่วงหน้า

คนอื่น ๆ กำลังทดลองวิธีใหม่ ๆ ในการใช้ LLVM เป็น JIT เช่นการรวบรวมแบบสอบถาม PostgreSQL ทำให้ประสิทธิภาพเพิ่มขึ้นถึงห้าเท่า

การเพิ่มประสิทธิภาพรหัสอัตโนมัติด้วย LLVM

LLVM ไม่เพียงแค่รวบรวม IR เข้ากับรหัสเครื่องดั้งเดิม นอกจากนี้คุณยังสามารถกำหนดทิศทางด้วยโปรแกรมเพื่อเพิ่มประสิทธิภาพโค้ดด้วยความละเอียดระดับสูงได้ตลอดกระบวนการเชื่อมโยง การเพิ่มประสิทธิภาพอาจค่อนข้างก้าวร้าวรวมถึงสิ่งต่างๆเช่นฟังก์ชัน inlining การกำจัดโค้ดที่ตายแล้ว (รวมถึงการประกาศประเภทที่ไม่ได้ใช้และอาร์กิวเมนต์ของฟังก์ชัน) และการคลายการวนซ้ำ

อีกครั้งอำนาจไม่จำเป็นต้องดำเนินการทั้งหมดนี้ด้วยตัวเอง LLVM สามารถจัดการให้คุณหรือคุณสามารถสั่งให้ปิดได้ตามต้องการ ตัวอย่างเช่นหากคุณต้องการไบนารีที่เล็กลงโดยมีค่าใช้จ่ายในการทำงานคุณสามารถให้ส่วนหน้าคอมไพเลอร์ของคุณบอก LLVM เพื่อปิดใช้งานการคลายการวนซ้ำ

ภาษาเฉพาะโดเมนที่มี LLVM

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

ตัวอย่างเช่นโครงการ Emscripten ใช้รหัส LLVM IR และแปลงเป็น JavaScript ในทางทฤษฎีอนุญาตให้ภาษาใด ๆ ที่มีส่วนหลัง LLVM เพื่อส่งออกโค้ดที่สามารถเรียกใช้ในเบราว์เซอร์ได้ แผนระยะยาวคือการมีแบ็คเอนด์แบบ LLVM ที่สามารถสร้าง WebAssembly ได้ แต่ Emscripten เป็นตัวอย่างที่ดีว่า LLVM มีความยืดหยุ่นได้อย่างไร

อีกวิธีหนึ่งที่สามารถใช้ LLVM คือการเพิ่มส่วนขยายเฉพาะโดเมนลงในภาษาที่มีอยู่ Nvidia ใช้ LLVM ในการสร้าง Nvidia CUDA Compiler ซึ่งช่วยให้ภาษาต่างๆสามารถเพิ่มการสนับสนุนดั้งเดิมสำหรับ CUDA ที่รวบรวมเป็นส่วนหนึ่งของโค้ดเนทีฟที่คุณสร้างขึ้น (เร็วกว่า) แทนที่จะเรียกใช้ผ่านไลบรารีที่มาพร้อมกับมัน (ช้ากว่า)

ความสำเร็จของ LLVM กับภาษาเฉพาะโดเมนได้กระตุ้นโครงการใหม่ภายใน LLVM เพื่อแก้ไขปัญหาที่พวกเขาสร้างขึ้น ปัญหาที่ใหญ่ที่สุดคือการที่ DSL บางตัวแปลเป็น LLVM IR ได้ยากโดยไม่ต้องทำงานหนักในส่วนหน้า โซลูชันหนึ่งในผลงานคือ Multi-Level Intermediate Representation หรือโครงการ MLIR

MLIR มีวิธีที่สะดวกในการแสดงโครงสร้างข้อมูลและการดำเนินการที่ซับซ้อนซึ่งสามารถแปลเป็น LLVM IR โดยอัตโนมัติ ตัวอย่างเช่นเฟรมเวิร์กการเรียนรู้ของเครื่อง TensorFlow อาจมีการดำเนินการ dataflow-graph ที่ซับซ้อนจำนวนมากที่คอมไพล์กับโค้ดเนทีฟด้วย MLIR

การทำงานกับ LLVM ในภาษาต่างๆ

วิธีทั่วไปในการทำงานกับ LLVM คือการใช้รหัสในภาษาที่คุณคุ้นเคย (และแน่นอนว่ารองรับไลบรารีของ LLVM)

ตัวเลือกภาษาทั่วไปสองภาษาคือ C และ C ++ นักพัฒนา LLVM หลายคนเริ่มต้นเป็นหนึ่งในสองข้อนี้ด้วยเหตุผลหลายประการ 

  • LLVM นั้นเขียนด้วยภาษา C ++
  • API ของ LLVM มีให้บริการในรูปแบบ C และ C ++
  • การพัฒนาภาษาส่วนใหญ่มักจะเกิดขึ้นโดยใช้ C / C ++ เป็นฐาน

ถึงกระนั้นทั้งสองภาษาก็ไม่ใช่ทางเลือกเดียว หลายภาษาสามารถเรียกโดยกำเนิดในไลบรารี C ได้ดังนั้นในทางทฤษฎีจึงเป็นไปได้ที่จะดำเนินการพัฒนา LLVM ด้วยภาษาดังกล่าว แต่จะช่วยให้มีไลบรารีจริงในภาษาที่ห่อหุ้ม API ของ LLVM อย่างหรูหรา โชคดีที่ภาษาและเวลาทำงานของภาษาหลายภาษามีไลบรารีเช่น C # /. NET / Mono, Rust, Haskell, OCAML, Node.js, Go และ Python

ข้อแม้ประการหนึ่งคือการผูกภาษากับ LLVM บางส่วนอาจสมบูรณ์น้อยกว่าภาษาอื่น ๆ ตัวอย่างเช่นด้วย Python มีทางเลือกมากมาย แต่แต่ละตัวจะแตกต่างกันไปตามความสมบูรณ์และอรรถประโยชน์:

  • llvmlite ซึ่งพัฒนาโดยทีมที่สร้าง Numba ได้กลายเป็นคู่แข่งในปัจจุบันสำหรับการทำงานกับ LLVM ใน Python ใช้เฉพาะส่วนย่อยของฟังก์ชันการทำงานของ LLVM ตามที่กำหนดโดยความต้องการของโครงการ Numba แต่ชุดย่อยนั้นให้สิ่งที่ผู้ใช้ LLVM ส่วนใหญ่ต้องการ (โดยทั่วไปแล้ว llvmlite เป็นตัวเลือกที่ดีที่สุดสำหรับการทำงานกับ LLVM ใน Python)
  • โครงการ LLVM คงชุดการเชื่อมโยงกับ C API ของ LLVM ไว้เป็นของตัวเอง แต่ตอนนี้ยังไม่ได้รับการบำรุงรักษา
  • llvmpy ซึ่งเป็น Python ที่ได้รับความนิยมตัวแรกสำหรับ LLVM หลุดจากการบำรุงรักษาในปี 2015 ไม่ดีสำหรับโครงการซอฟต์แวร์ใด ๆ แต่แย่ลงเมื่อทำงานกับ LLVM เนื่องจากจำนวนการเปลี่ยนแปลงที่เกิดขึ้นใน LLVM แต่ละรุ่น
  • llvmcpy มีจุดมุ่งหมายเพื่อนำการผูก Python สำหรับไลบรารี C ให้ทันสมัยอัปเดตด้วยวิธีอัตโนมัติและทำให้สามารถเข้าถึงได้โดยใช้สำนวนพื้นเมืองของ Python llvmcpy ยังอยู่ในขั้นเริ่มต้น แต่ก็สามารถทำงานเบื้องต้นกับ LLVM API ได้แล้ว

หากคุณอยากรู้เกี่ยวกับวิธีใช้ไลบรารี LLVM เพื่อสร้างภาษาผู้สร้างของ LLVM เองก็มีบทช่วยสอนโดยใช้ C ++ หรือ OCAML ซึ่งจะช่วยให้คุณสร้างภาษาง่ายๆที่เรียกว่า Kaleidoscope มันถูกย้ายไปเป็นภาษาอื่น:

  • Haskell: พอร์ตโดยตรงของบทช่วยสอนดั้งเดิม
  • Python:พอร์ตหนึ่งดังกล่าวติดตามบทช่วยสอนอย่างใกล้ชิดในขณะที่อีกพอร์ตหนึ่งเป็นการเขียนซ้ำที่ทะเยอทะยานกว่าด้วยบรรทัดคำสั่งแบบโต้ตอบ ทั้งสองอย่างนี้ใช้ llvmlite เป็นตัวเชื่อมกับ LLVM
  • Rust  and  Swift:ดูเหมือนจะหลีกเลี่ยงไม่ได้ที่เราจะได้รับพอร์ตการสอนเป็นสองภาษาที่ LLVM ช่วยให้เกิด

ในที่สุดบทช่วยสอนยังมีให้บริการใน  ภาษาของมนุษย์ ได้รับการแปลเป็นภาษาจีนโดยใช้ C ++ และ Python ดั้งเดิม

LLVM ไม่ทำอะไร

ด้วยสิ่งที่ LLVM มีให้การรู้ว่ามันไม่ทำอะไร

ตัวอย่างเช่น LLVM ไม่แยกวิเคราะห์ไวยากรณ์ของภาษา เครื่องมือหลายอย่างทำงานนั้นอยู่แล้วเช่น lex / yacc, flex / bison, Lark และ ANTLR การแยกวิเคราะห์นั้นหมายถึงการแยกออกจากการรวบรวมดังนั้นจึงไม่น่าแปลกใจที่ LLVM ไม่ได้พยายามแก้ไขปัญหานี้

LLVM ยังไม่ได้กล่าวถึงวัฒนธรรมซอฟต์แวร์ที่มีขนาดใหญ่ขึ้นในภาษาที่กำหนดโดยตรง การติดตั้งไบนารีของคอมไพลเลอร์การจัดการแพ็คเกจในการติดตั้งและการอัพเกรดโซ่เครื่องมือคุณต้องดำเนินการด้วยตัวเอง

สุดท้ายและที่สำคัญที่สุดยังคงมีบางส่วนของภาษาทั่วไปที่ LLVM ไม่ได้จัดเตรียมพื้นฐานไว้ให้ หลายภาษามีลักษณะการจัดการหน่วยความจำที่เก็บรวบรวมขยะไม่ว่าจะเป็นวิธีหลักในการจัดการหน่วยความจำหรือเป็นส่วนเสริมของกลยุทธ์เช่น RAII (ซึ่ง C ++ และ Rust ใช้) LLVM ไม่ได้ให้กลไกการรวบรวมขยะแก่คุณ แต่มีเครื่องมือในการดำเนินการเก็บขยะโดยอนุญาตให้ทำเครื่องหมายรหัสด้วยข้อมูลเมตาที่ทำให้การเขียนตัวรวบรวมขยะง่ายขึ้น

อย่างไรก็ตามสิ่งนี้ไม่ได้กำหนดความเป็นไปได้ที่ LLVM อาจเพิ่มกลไกดั้งเดิมสำหรับการดำเนินการเก็บขยะในที่สุด LLVM กำลังพัฒนาอย่างรวดเร็วโดยมีการเปิดตัวครั้งใหญ่ทุกๆหกเดือนหรือมากกว่านั้น และความก้าวหน้าของการพัฒนามีแนวโน้มที่จะดีขึ้นเนื่องจากภาษาปัจจุบันหลายภาษาได้กำหนดให้ LLVM เป็นหัวใจของกระบวนการพัฒนา