LLVMとは何ですか?Swift、Rust、Clangなどの背後にある力

新しい言語、および既存の言語の改善は、開発環境全体で急成長しています。MozillaのRust、AppleのSwift、JetbrainsのKotlin、および他の多くの言語は、開発者に速度、安全性、利便性、移植性、およびパワーに関する新しい選択肢を提供します。

なぜ今なのか?大きな理由の1つは、言語を構築するための新しいツール、特にコンパイラーです。その中で最も重要なのは、イリノイ大学の研究プロジェクトとしてSwift言語の作成者であるChrisLattnerによって最初に開発されたオープンソースプロジェクトであるLLVMです。

LLVMを使用すると、新しい言語を簡単に作成できるだけでなく、既存の言語の開発を強化できます。コンパイラの作成、出力されたコードの複数のプラットフォームとアーキテクチャへの移植、ベクトル化などのアーキテクチャ固有の最適化の生成、次のような一般的な言語の比喩を処理するコードの記述など、言語作成タスクの最もありがたい部分の多くを自動化するためのツールを提供します。例外。その自由なライセンスは、ソフトウェアコンポーネントとして自由に再利用したり、サービスとして展開したりできることを意味します。

LLVMを利用する言語の名簿には、多くのよく知られた名前があります。AppleのSwift言語はコンパイラフレームワークとしてLLVMを使用し、RustはツールチェーンのコアコンポーネントとしてLLVMを使用します。また、多くのコンパイラには、Clang、C / C ++コンパイラ(この名前は「C-lang」)などのLLVMエディションがあり、それ自体がLLVMと密接に関連するプロジェクトです。.NET実装であるMonoには、LLVMバックエンドを使用してネイティブコードにコンパイルするオプションがあります。また、名目上JVM言語であるKotlinは、LLVMを使用してマシンネイティブコードにコンパイルするKotlinNativeと呼ばれる言語のバージョンを開発しています。

LLVMが定義されました

LLVMは、本質的に、プログラムでマシンネイティブコードを作成するためのライブラリです。開発者はAPIを使用して、中間表現(IR)と呼ばれる形式で命令を生成します。次に、LLVMは、IRをスタンドアロンバイナリにコンパイルするか、コードに対してJIT(ジャストインタイム)コンパイルを実行して、言語のインタープリターやランタイムなどの別のプログラムのコンテキストで実行できます。

LLVMのAPIは、プログラミング言語に見られる多くの一般的な構造とパターンを開発するためのプリミティブを提供します。たとえば、ほとんどすべての言語には関数とグローバル変数の概念があり、多くの言語にはコルーチンとCの外部関数インターフェイスがあります。LLVMには、IRの標準要素として関数とグローバル変数があり、コルーチンを作成してCライブラリとインターフェイスするためのメタファーがあります。

これらの特定のホイールを再発明するために時間とエネルギーを費やす代わりに、LLVMの実装を使用して、注意が必要な言語の部分に集中することができます。

Go、Kotlin、Python、Rustについてもっと読む 

行く:

  • GoogleのGo言語の力を活用する
  • 最高のGo言語IDEとエディター

Kotlin:

  • Kotlinとは何ですか?Javaの代替案は説明しました
  • Kotlinフレームワーク:JVM開発ツールの調査

Python:

  • Pythonとは何ですか?あなたが知る必要があるすべて
  • チュートリアル:Pythonの使用を開始する方法
  • すべてのPython開発者のための6つの必須ライブラリ

さび:

  • Rustとは何ですか?安全、迅速、簡単なソフトウェア開発を行う方法
  • Rustを使い始める方法を学ぶ 

LLVM:移植性のために設計されています

LLVMを理解するには、Cプログラミング言語との類似性を検討すると役立つ場合があります。Cは、システムハードウェアに密接にマッピングできる構造を持ち、ほとんどに移植されているため、ポータブルで高レベルのアセンブリ言語として説明されることがあります。すべてのシステムアーキテクチャ。しかし、Cは、ある程度までは移植可能なアセンブリ言語としてのみ役立ちます。それはその特定の目的のために設計されたものではありません。

対照的に、LLVMのIRは、最初からポータブルアセンブリとして設計されていました。この移植性を実現する1つの方法は、特定のマシンアーキテクチャに依存しないプリミティブを提供することです。たとえば、整数型は、基盤となるハードウェアの最大ビット幅(32ビットや64ビットなど)に限定されません。128ビット整数のように、必要な数のビットを使用してプリミティブ整数型を作成できます。また、特定のプロセッサの命令セットに一致するように出力を作成することについて心配する必要はありません。LLVMがそれを処理します。

LLVMのアーキテクチャに依存しない設計により、現在および将来のあらゆる種類のハードウェアのサポートが容易になります。たとえば、IBMは最近、z / OS、Linux on Power(IBMのMASSベクトル化ライブラリーのサポートを含む)、およびLLVMのC、C ++、およびFortranプロジェクト用のAIXアーキテクチャーをサポートするコードを提供しました。 

LLVM IRのライブ例を見たい場合は、ELLCCプロジェクトのWebサイトにアクセスして、ブラウザーでCコードをLLVMIRに変換するライブデモを試してください。

プログラミング言語がLLVMを使用する方法

LLVMの最も一般的な使用例は、言語の事前(AOT)コンパイラーとしてです。たとえば、Clangプロジェクトは、事前にCおよびC ++をネイティブバイナリにコンパイルします。しかし、LLVMは他のことも可能にします。

LLVMを使用したジャストインタイムコンパイル

一部の状況では、コードを事前にコンパイルするのではなく、実行時にオンザフライで生成する必要があります。たとえば、Julia言語は、高速で実行し、REPL(read-eval-print loop)または対話型プロンプトを介してユーザーと対話する必要があるため、コードをJITコンパイルします。 

Python用の数学アクセラレーションパッケージであるNumbaは、選択したPython関数を機械語にコンパイルします。Numbaで装飾されたコードを事前にコンパイルすることもできますが、(Juliaのように)Pythonは、インタープリター言語であるため、迅速な開発を提供します。JITコンパイルを使用してこのようなコードを生成すると、事前コンパイルよりもPythonのインタラクティブワークフローが補完されます。

他の企業は、PostgreSQLクエリのコンパイルなど、LLVMをJITとして使用する新しい方法を実験しており、パフォーマンスが最大5倍向上しています。

LLVMによる自動コード最適化

LLVMは、IRをネイティブマシンコードにコンパイルするだけではありません。また、リンクプロセス全体を通じて、コードを高度な粒度で最適化するようにプログラムで指示することもできます。最適化は、関数のインライン化、デッドコードの排除(未使用の型宣言や関数の引数を含む)、ループの展開など、非常に積極的になる可能性があります。

繰り返しますが、力はこれらすべてを自分で実装する必要がないことにあります。LLVMはそれらを処理するか、必要に応じてそれらをオフに切り替えるように指示することができます。たとえば、パフォーマンスを犠牲にしてより小さなバイナリが必要な場合は、コンパイラのフロントエンドにLLVMにループ展開を無効にするように指示させることができます。

LLVMを使用したドメイン固有言語

LLVMは、多くの汎用言語用のコンパイラーを作成するために使用されてきましたが、非常に垂直的または問題のあるドメイン専用の言語を作成する場合にも役立ちます。ある意味で、これはLLVMが最も輝いているところです。なぜなら、LLVMは、そのような言語を作成する際の多くの煩わしさを取り除き、パフォーマンスを向上させるからです。

たとえば、Emscriptenプロジェクトは、LLVM IRコードを取得してJavaScriptに変換します。理論的には、LLVMバックエンドを備えたすべての言語で、ブラウザー内で実行できるコードをエクスポートできます。長期的な計画では、WebAssemblyを生成できるLLVMベースのバックエンドを用意する予定ですが、EmscriptenはLLVMの柔軟性の良い例です。

LLVMを使用できるもう1つの方法は、既存の言語にドメイン固有の拡張機能を追加することです。NvidiaはLLVMを使用してNvidiaCUDAコンパイラを作成しました。これにより、言語は、付属のライブラリから呼び出されるのではなく(低速)、生成するネイティブコードの一部としてコンパイルされる(高速)CUDAのネイティブサポートを追加できます。

ドメイン固有言語でのLLVMの成功は、LLVM内の新しいプロジェクトに拍車をかけ、それらが生み出す問題に対処しました。最大の問題は、フロントエンドで多くのハードワークを行わないと、一部のDSLをLLVMIRに変換するのが難しいことです。作業中の1つの解決策は、マルチレベル中間表現(MLIRプロジェクト)です。

MLIRは、複雑なデータ構造と操作を表す便利な方法を提供し、それらを自動的にLLVMIRに変換できます。たとえば、TensorFlow機械学習フレームワークでは、複雑なデータフローグラフ操作の多くをMLIRを使用してネイティブコードに効率的にコンパイルできます。

さまざまな言語でのLLVMの操作

LLVMを操作する一般的な方法は、使い慣れた言語のコードを使用することです(もちろん、LLVMのライブラリーもサポートされています)。

2つの一般的な言語の選択肢は、CとC ++です。多くのLLVM開発者は、いくつかの理由から、デフォルトでこれら2つのうちの1つを使用します。 

  • LLVM自体はC ++で記述されています。
  • LLVMのAPIは、CおよびC ++の化身で利用できます。
  • 多くの言語開発は、C / C ++をベースとして発生する傾向があります

それでも、これら2つの言語だけが選択肢ではありません。多くの言語はCライブラリをネイティブに呼び出すことができるため、理論的には、そのような言語でLLVM開発を実行することが可能です。ただし、LLVMのAPIをエレガントにラップする言語で実際のライブラリを用意すると便利です。幸い、C#/。NET / Mono、Rust、Haskell、OCAML、Node.js、Go、Pythonなど、多くの言語と言語ランタイムにそのようなライブラリがあります。

注意点の1つは、LLVMへの言語バインディングの一部が他のバインディングよりも不完全である可能性があることです。たとえば、Pythonには多くの選択肢がありますが、それぞれの完全性と有用性は異なります。

  • Numbaを作成するチームによって開発されたllvmliteは、PythonでLLVMを操作するための現在の候補として浮上しています。Numbaプロジェクトのニーズに応じて、LLVMの機能のサブセットのみを実装します。しかし、そのサブセットは、LLVMユーザーが必要とするものの大部分を提供します。(一般的に、PythonでLLVMを操作するにはllvmliteが最適です。)
  • LLVMプロジェクトは、LLVMのC APIへの独自のバインディングのセットを維持していますが、現在は維持されていません。
  • LLVM用の最初の人気のあるPythonバインディングであるllvmpyは、2015年にメンテナンスから外れました。どのソフトウェアプロジェクトにも適していませんが、LLVMの各エディションで行われる変更の数を考えると、LLVMで作業する場合はさらに悪くなります。
  • llvmcpyは、CライブラリのPythonバインディングを最新の状態にし、自動化された方法で更新し、Pythonのネイティブイディオムを使用してアクセスできるようにすることを目的としています。llvmcpyはまだ初期段階ですが、LLVMAPIを使用してすでにいくつかの基本的な作業を行うことができます。

LLVMライブラリを使用して言語を構築する方法に興味がある場合は、LLVMの作成者が、C ++またはOCAMLを使用して、Kaleidoscopeと呼ばれる単純な言語を作成する手順を説明するチュートリアルを用意しています。それ以来、他の言語に移植されています。

  • Haskell: 元のチュートリアルの直接移植。
  • Python:そのようなポートの1つはチュートリアルに厳密に従いますが、もう1つはインタラクティブなコマンドラインを使用したより野心的な書き直しです。これらは両方とも、LLVMへのバインディングとしてllvmliteを使用します。
  • Rust  and  Swift: LLVMが実現するのに役立った2つの言語へのチュートリアルの移植を取得することは避けられないように思われました。

最後に、チュートリアルは人間の言語でも利用できます 。オリジナルのC ++とPythonを使用して、中国語に翻訳されています。

LLVMが行わないこと

LLVMが提供するすべての機能を備えているので、LLVMが何をしないかを知ることも役立ちます。

たとえば、LLVMは言語の文法を解析しません。lex / yacc、flex / bison、Lark、ANTLRなど、多くのツールがすでにその仕事をしています。解析はとにかくコンパイルから切り離されることを意図しているので、LLVMがこれに対処しようとしないのは当然のことです。

LLVMは、特定の言語に関するソフトウェアのより大きな文化にも直接対応していません。コンパイラのバイナリのインストール、インストール内のパッケージの管理、およびツールチェーンのアップグレード-これは自分で行う必要があります。

最後に、そして最も重要なこととして、LLVMがプリミティブを提供しない言語の一般的な部分がまだあります。多くの言語には、メモリを管理するための主な方法として、またはRAII(C ++やRustが使用する)のような戦略の補助として、何らかの方法でガベージコレクションされたメモリ管理があります。LLVMはガベージコレクターメカニズムを提供しませんが、ガベージコレクターの作成を容易にするメタデータでコードをマークできるようにすることで、ガベージコレクションを実装するためのツールを提供します。

ただし、これはいずれも、LLVMが最終的にガベージコレクションを実装するためのネイティブメカニズムを追加する可能性を排除するものではありません。LLVMは急速に発展しており、6か月ごとにメジャーリリースがあります。そして、現在の多くの言語がLLVMを開発プロセスの中心に置いているおかげで、開発のペースは加速する可能性があります。