۳۰ شهریور ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

LLVM چیست؟ قدرت پشت Swift، Rust، Clang، و بیشتر

LLVM یک چارچوب کامپایلر برای تولید کدهای بومی ماشین به صورت برنامه‌نویسی است. توسعه دهندگان از آن برای ارائه زبان های جدید و تقویت زبان های موجود استفاده می کنند.

LLVM یک چارچوب کامپایلر برای تولید کدهای بومی ماشین به صورت برنامه‌نویسی است. توسعه دهندگان از آن برای ارائه زبان های جدید و تقویت زبان های موجود استفاده می کنند.

چشم انداز توسعه با زبان های جدید و پیشرفت هایی در زبان های موجود آماده شده است. Mozilla’s Rust، Apple’s Swift، Kotlin از JetBrains و نوع آزمایشی پایتون Mojo (و بسیاری دیگر) پیشنهاد می کنند توسعه دهندگان طیف گسترده ای از انتخاب ها را برای سرعت، ایمنی، راحتی، قابلیت حمل و قدرت دارند.

ورود ابزارهای جدید برای ساختن زبان ها – به ویژه کامپایلرها – عامل بزرگی در این فراوانی است. و مهمترین کامپایلرها LLVM است، یک پروژه متن باز که در ابتدا توسط کریس لاتنر، خالق زبان سوئیفت در دانشگاه ایلینویز توسعه یافت.< /p>

LLVM نه تنها ایجاد زبان‌های جدید، بلکه توسعه زبان‌های موجود را آسان‌تر می‌کند. این ابزار ابزارهایی را برای خودکارسازی بسیاری از بخش‌های ناسپاس در ایجاد یک زبان جدید فراهم می‌کند: توسعه یک کامپایلر، انتقال کد خروجی به پلتفرم‌ها و معماری‌های متعدد، تولید بهینه‌سازی‌های خاص معماری مانند برداری، و نوشتن کد برای مدیریت استعاره‌های زبان رایج مانند استثناها. . مجوز آزاد آن به این معنی است که LLVM می تواند آزادانه به عنوان یک جزء نرم افزاری یا به عنوان یک سرویس استفاده مجدد شود.

فهرست زبان‌هایی که از LLVM استفاده می‌کنند شامل بسیاری از نام‌های آشنا است. زبان سوئیفت اپل از LLVM به عنوان چارچوب کامپایلر خود استفاده می کند و Rust از آن به عنوان جزء اصلی زنجیره ابزار Rust استفاده می کند. بسیاری از کامپایلرها از LLVM از جمله Clang، کامپایلر C/C++ (از این رو نام “C-lang”) استفاده می کنند. Mono، پیاده سازی دات نت، گزینه ای برای کامپایل به بومی دارد. کد با استفاده از یک بک اند LLVM. و Kotlin که اسماً یک زبان JVM است، یک فناوری کامپایلر به نام Kotlin/Native ارائه می‌کند که از LLVM برای کامپایل کردن به کد بومی ماشین استفاده می‌کند.

LLVM چیست؟

در قلب، LLVM کتابخانه ای برای ایجاد کدهای بومی ماشین به صورت برنامه ای است. یک توسعه دهنده از API برای تولید دستورالعمل ها در قالبی به نام نمایش متوسط یا IR استفاده می کند. سپس LLVM می‌تواند IR را در یک باینری مستقل کامپایل کند یا یک کامپایل JIT (در زمان مقرر) روی کد انجام دهد تا در متن برنامه دیگری اجرا شود، مانند مفسر یا زمان اجرا برای زبان.

APIهای LLVM برای توسعه بسیاری از ساختارها و الگوهای رایج موجود در زبان‌های برنامه‌نویسی، موارد اولیه را ارائه می‌دهند. به عنوان مثال، تقریباً هر زبانی مفهوم تابع و متغیر سراسری را دارد و بسیاری از آنها دارای برنامه‌های مشترک و رابط‌های تابع خارجی C هستند. LLVM دارای توابع و متغیرهای سراسری به عنوان عناصر استاندارد در IR خود است و استعاره هایی برای ایجاد کوروتین ها و رابط با کتابخانه های C دارد.

به‌جای صرف وقت خود برای اختراع مجدد آن چرخ‌ها، می‌توانید فقط از پیاده‌سازی‌های LLVM استفاده کنید و روی بخش‌هایی از زبان خود که نیاز به توجه بیشتری دارند تمرکز کنید.

hello world llvm

نمونه‌ای از نمایش میانی LLVM (IR). در سمت راست یک برنامه ساده در C وجود دارد. در سمت چپ همان کدی است که توسط کامپایلر Clang به LLVM IR ترجمه شده است.

LLVM برای قابلیت حمل طراحی شده است

برای درک LLVM، ممکن است در نظر گرفتن یک قیاس با زبان برنامه نویسی C کمک کند: C گاهی اوقات به عنوان یک زبان اسمبلی قابل حمل و سطح بالا توصیف می شود. این به این دلیل است که C ساختارهایی دارد که می‌تواند به سخت‌افزار سیستم نزدیک شود و تقریباً به هر معماری سیستم منتقل شده است. اما C به عنوان یک زبان اسمبلی قابل حمل فقط تا حدی مفید است. برای آن هدف خاص طراحی نشده است.

مایکروسافت پروکسی Graph API را برای توسعه دهندگان پیش نمایش می کند

در مقابل، IR LLVM از ابتدا به عنوان یک مجموعه قابل حمل طراحی شده بود. یکی از راه‌هایی که این قابلیت حمل را انجام می‌دهد، ارائه اولیه‌های مستقل از معماری خاص ماشین است. به عنوان مثال، انواع عدد صحیح به حداکثر عرض بیت سخت افزار زیرین (مانند ۳۲ یا ۶۴ بیت) محدود نمی شوند. شما می توانید انواع اعداد صحیح اولیه را با استفاده از هر تعداد بیت که نیاز دارید، مانند یک عدد صحیح ۱۲۸ بیتی ایجاد کنید. همچنین لازم نیست نگران ایجاد خروجی برای مطابقت با مجموعه دستورات پردازنده خاص باشید. LLVM از آن برای شما مراقبت می کند.

طراحی خنثی از نظر معماری LLVM پشتیبانی از انواع سخت افزارها، حال و آینده را آسان تر می کند. برای مثال، IBM کد ارائه کرده برای پشتیبانی از z/OS، Linux on Power (شامل پشتیبانی از IBM کتابخانه MASS vectorization)، و معماری های AIX برای پروژه های C، C++ و Fortran LLVM.

اگر می‌خواهید نمونه‌های زنده LLVM IR را ببینید، می‌توانید به سایت کاوشگر کامپایلر Godbolt بروید و ترجمه کنید. C یا C++ به LLVM IR. Clang را به عنوان کامپایلر انتخاب کنید، سپس از افزودن جدید، LLVM IR را انتخاب کنید تا برگه ای باز شود که کد LLVM IR تولید شده از کد C/C++ ارائه شده را نشان می دهد.

چگونه زبان های برنامه نویسی از LLVM استفاده می کنند

رایج ترین مورد استفاده برای LLVM به عنوان یک کامپایلر زبان پیش از زمان (AOT) است. به عنوان مثال، پروژه Clang از قبل C و C++ را به باینری های بومی کامپایل می کند. اما LLVM چیزهای دیگری را نیز ممکن می کند.

کامپایل به‌موقع با LLVM

بعضی شرایط به جای کامپایل کردن زودتر از موعد نیاز به تولید کد در زمان اجرا دارند. برای مثال، زبان جولیا، JIT کد خود را کامپایل می‌کند، زیرا باید سریع اجرا شود و از طریق یک REPL (حلقه خواندن-ایوال-چاپ) یا اعلان تعاملی با کاربر تعامل داشته باشد.

Numba، یک بسته شتاب ریاضی برای پایتون، JIT توابع انتخاب شده پایتون را در کد ماشین کامپایل می‌کند. همچنین می‌تواند کدهای تزئین شده با Numba را زودتر از موعد کامپایل کند، اما (مانند جولیا) پایتون با بودن یک زبان تفسیر شده، توسعه سریعی را ارائه می‌دهد. استفاده از کامپایل JIT برای تولید چنین کدی، گردش کار تعاملی پایتون را بهتر از کامپایل‌سازی زودهنگام تکمیل می‌کند.

دیگران در حال آزمایش راه‌های جدیدی برای استفاده از LLVM به عنوان یک کامپایلر JIT هستند، مانند کامپایل پرس‌و‌جوهای PostgreSQL، که عملکرد را تا پنج برابر افزایش می‌دهد.

numba llvm

Numba از LLVM برای کامپایل به موقع کدهای عددی و تسریع در اجرای آن استفاده می کند. تابع sum2d با سرعت JIT یک اجرا را حدوداً ۱۳۹ برابر سریعتر از کد معمولی پایتون تکمیل می‌کند.

بهینه سازی خودکار کد با LLVM

LLVM فقط IR را به کد ماشین بومی کامپایل نمی کند. همچنین می‌توانید به صورت برنامه‌نویسی آن را برای بهینه‌سازی کد با درجه بالایی از جزئیات، در تمام طول فرآیند پیوند هدایت کنید. بهینه‌سازی‌ها می‌توانند کاملاً تهاجمی باشند، از جمله مواردی مانند توابع درون خطی، حذف کد مرده (از جمله اعلان‌های نوع استفاده نشده و آرگومان‌های تابع)، و باز کردن حلقه‌ها.

باز هم، قدرت این است که مجبور نباشید همه اینها را خودتان پیاده کنید. LLVM می‌تواند آن وظایف را برای شما انجام دهد، یا می‌توانید آن را هدایت کنید تا در صورت نیاز آن‌ها را خاموش و روشن کنید. به عنوان مثال، اگر می‌خواهید باینری‌های کوچک‌تری را به قیمت کمی کارایی انجام دهید، می‌توانید از کامپایلر خود بخواهید به LLVM بگوید باز کردن حلقه را غیرفعال کند.

IBM از Micro Focus به دلیل نقض حق نسخه برداری نرم افزار مرکزی شکایت کرد

زبان های دامنه خاص با LLVM

LLVM برای تولید کامپایلر برای بسیاری از زبان‌های همه منظوره استفاده شده است، اما برای تولید زبان‌هایی که بسیار عمودی یا انحصاری برای یک دامنه مشکل هستند نیز مفید است. از برخی جهات، اینجا جایی است که LLVM بیشترین درخشش را دارد، زیرا بسیاری از مشقت‌های ایجاد چنین زبانی را از بین می‌برد و باعث می‌شود عملکرد خوبی داشته باشد.

برای مثال،

پروژه Emscripten، کد LLVM IR را می گیرد و آن را به جاوا اسکریپت تبدیل می کند، در تئوری اجازه می دهد هر زبانی با کد LLVM برای صادرات که بتواند در مرورگر اجرا شود. یکی از نتایج بلندمدت این کار، بک‌اندهای مبتنی بر LLVM است که می‌توانند WebAssembly را تولید کنند و به زبان‌هایی مانند Rust اجازه می‌دهند مستقیماً به‌عنوان هدف در WASM کامپایل شوند.

راه دیگری که می توان از LLVM استفاده کرد افزودن پسوندهای خاص دامنه به یک زبان موجود است. انویدیا از LLVM برای ایجاد کامپایلر Nvidia CUDA استفاده کرد، که به زبان‌ها اجازه می‌دهد پشتیبانی بومی برای CUDA اضافه کنند. به‌عنوان بخشی از کد بومی که تولید می‌کنید (سریع‌تر)، به‌جای فراخوانی از طریق کتابخانه‌ای که با آن ارسال می‌شود (آهسته‌تر) کامپایل می‌شود.

موفقیت LLVM با زبان‌های خاص دامنه، پروژه‌های جدیدی را در LLVM برای رسیدگی به مشکلاتی که ایجاد می‌کنند تحریک کرده است. بزرگترین مشکل این است که چگونه برخی از DSL ها به سختی به LLVM IR ترجمه می شوند، بدون اینکه کار سختی زیادی در قسمت جلویی داشته باشند. یکی از راه حل های در حال کار، نمایندگی چندسطحی متوسط ​​یا پروژه MLIR است.

MLIR راه‌های مناسبی را برای نمایش ساختارها و عملیات پیچیده داده ارائه می‌کند، که می‌تواند به طور خودکار به LLVM IR ترجمه شود. به عنوان مثال، چارچوب یادگیری ماشینی TensorFlow می‌تواند بسیاری از عملیات پیچیده جریان داده‌های گراف خود را به طور موثر در کدهای بومی با MLIR کامپایل کند.

نحوه کار با LLVM

روش معمولی برای کار با LLVM از طریق کد به زبانی است که از کتابخانه های LLVM پشتیبانی می کند.

دو زبان رایج C و C++ هستند. بسیاری از توسعه‌دهندگان LLVM به دلایل خوب یکی از این دو را پیش‌فرض می‌کنند: 

  • خود LLVM در C++ نوشته شده است.
  • APIهای LLVM در نسخه‌های C و C++ موجود هستند.
  • توسعه زبان بیشتر با C/C++ به عنوان پایه اتفاق می افتد.

با این وجود، این دو زبان تنها انتخاب نیستند. بسیاری از زبان‌ها می‌توانند به صورت بومی با کتابخانه‌های C تماس بگیرند، بنابراین از نظر تئوری می‌توان توسعه LLVM را با هر زبانی انجام داد. اما داشتن یک کتابخانه واقعی به زبانی که به زیبایی APIهای LLVM را می‌پیچد، کمک می‌کند. خوشبختانه، بسیاری از زبان‌ها و زمان‌های اجرا زبان چنین کتابخانه‌هایی دارند، از جمله C#/.NET/Mono، زنگ، Haskell، OCAML، Node.js، برو و Python.

یک اخطار این است که برخی از پیوندهای زبانی به LLVM ممکن است نسبت به بقیه کاملتر باشند. برای مثال، با پایتون، انتخاب‌های زیادی در طول زمان ظاهر می‌شوند، که از نظر کامل و کاربردی متفاوت هستند و یک رقیب برتر ظاهر می‌شود.

  • llvmlite که توسط تیم سازنده Numba توسعه یافته است، به عنوان رقیب فعلی برای کار با LLVM ظاهر شده است. در پایتون این تنها زیرمجموعه ای از عملکرد LLVM را اجرا می کند، همانطور که توسط نیازهای پروژه Numba دیکته شده است. اما این زیر مجموعه اکثریت قریب به اتفاق نیاز کاربران LLVM را فراهم می کند. برای این منظور، llvmlite به طور کلی بهترین انتخاب برای کار با LLVM در پایتون است.
  • پروژه LLVM مجموعه اتصالات خاص خود را دارد به C API LLVM، اما در حال حاضر نگهداری نمی شوند.
  • llvmpy، اولین پیوند محبوب پایتون برای LLVM، در سال ۲۰۱۵ از تعمیر خارج شد. این برای هر پروژه نرم افزاری، اما بدتر از کار با LLVM، با توجه به تعداد تغییراتی که در هر نسخه جدید ایجاد می شود.
  • llvmcpy هدف دارد پیوندهای Python را برای کتابخانه C به‌روز کند، آنها را در یک به‌روزرسانی نگه دارد. به روشی خودکار، و با استفاده از اصطلاحات بومی پایتون، آنها را در دسترس قرار دهید. llvmcpy در حال حاضر می‌تواند کارهای ابتدایی را با APIهای LLVM انجام دهد، اما از سال ۲۰۱۹ به‌روزرسانی نشده است.
با «نسخه سوم دوره سقوط پایتون» وارد پایتون شوید

اگر کنجکاو هستید که چگونه از کتابخانه های LLVM برای ساختن زبان استفاده کنید، سازندگان LLVM یک دارند. آموزش، با استفاده از C++ یا OCAML، که شما را در ایجاد یک زبان ساده به نام Kaleidoscope راهنمایی می کند. از آن زمان به زبان های دیگر منتقل شده است:

  • Haskell: پورت مستقیم آموزش اصلی.
  • Python: یکی از این پورت‌ها آموزش را از نزدیک دنبال می‌کند، در حالی که دیگری یک بازنویسی بلندپروازانه تر با یک خط فرمان تعاملی. هر دوی آنها از llvmlite به عنوان اتصال به LLVM استفاده می کنند.
  • زنگ و سوئیفت: به نظر می رسید که این کار اجتناب ناپذیر بود. پورت های آموزش را به دو زبان از زبان هایی که LLVM به ایجاد آنها کمک کرده است.

در نهایت، آموزش LLVM به زبان‌های انسانی نیز موجود است. با استفاده از C++ اصلی و پایتون.

آنچه LLVM انجام نمی دهد

با همه چیزهایی که LLVM ارائه می دهد، دانستن اینکه چه کارهایی انجام نمی دهد نیز مفید است.

برای مثال، LLVM گرامر یک زبان را تجزیه نمی‌کند. بسیاری از ابزارها از قبل این کار را انجام می دهند، مانند lex/yacc، flex/bison، Lark، و ANTLR. به هر حال قرار است تجزیه از کامپایل جدا شود، بنابراین جای تعجب نیست که LLVM سعی نمی کند به هیچ یک از این موارد رسیدگی کند.

LLVM همچنین مستقیماً به فرهنگ بزرگتر نرم افزار در اطراف یک زبان معین نمی پردازد. نصب باینری‌های کامپایلر، مدیریت بسته‌ها در یک نصب، و ارتقای زنجیره ابزار—باید همه این کارها را خودتان انجام دهید.

در نهایت، و مهم‌تر از همه، هنوز بخش‌های مشترکی از زبان‌ها وجود دارد که LLVM برای آنها موارد اولیه ارائه نمی‌کند. بسیاری از زبان‌ها دارای نوعی مدیریت حافظه جمع‌آوری‌شده هستند، یا به عنوان راه اصلی مدیریت حافظه یا به عنوان مکمل راهبردهایی مانند RAII (که C++ و Rust از آن استفاده می کنند). LLVM مکانیزم جمع‌آوری زباله در اختیار شما قرار نمی‌دهد، اما ابزارهایی را برای اجرای جمع‌آوری زباله ارائه می‌کند. با اجازه دادن به کد برای علامت گذاری با ابرداده که نوشتن زباله گردها را آسان تر می کند.

هیچ‌کدام از این موارد این احتمال را که LLVM ممکن است در نهایت مکانیسم‌های بومی را برای اجرای جمع‌آوری زباله اضافه کند را رد نمی‌کند. LLVM به سرعت در حال توسعه است و هر شش ماه یک بار منتشر می شود. و سرعت توسعه احتمالاً فقط به لطف روشی که بسیاری از زبان‌های فعلی LLVM را در قلب فرآیند توسعه خود قرار داده‌اند افزایش می‌یابد.