LLVM:Swift、Rust等語言背後那個默默付出的女人

作者|Serdar Yegulalp 編輯|蓋磊

這幾年來,一些新的開發語言如雨後春筍般湧現,比如 Mozilla 的 Rust、Apple 的 Swift 以及 Jetbrains 的 Kotlin 等等,當然很多好的語言也在不斷迭代,比如 Java。這些語言為開發人員在開發速度、安全性、便利性、可移植性和功能上提供了多種選擇。

這幾年編程語言的發展速度為什麼這麼快?我覺得其中一個重要原因,就是我們具備了構建語言尤其是 編譯器的新工具,其中首屈一指的就是 LLVM(Low-Level Virtual Machine)。LLVM 是一個開源項目,最初是由 Swift 語言創始人 Chris Lattner 以伊利諾伊大學的一個研究項目為基礎發展而來。

LLVM 不僅簡化了新語言的創建工作,而且提升了現有語言的發展。它提供了一種工具,自動化了創建語言任務中許多最吃力的部分,包括創建編譯器、將輸出代碼移植到多個平台和架構上,以及編寫代碼實現異常處理這樣的常見語言隱喻(metaphor)。LLVM 是自由許可的,這意味著它可作為軟體組件自由重用,也可以作為服務自由部署。

如果列出一份使用了 LLVM 的語言清單,我們能從中看到許多耳熟能詳的名字。例如,Apple 的 Swift 語言使用 LLVM 作為編譯器框架,Rust 使用 LLVM 作為工具鏈的核心組件。此外,很多編譯器也提供了 LLVM 版本。例如,Clang 這個 C/C++ 編譯器本身就是一個以 LLVM 為準繩的項目。還有 Kotlin,它名義上是一種 JVM 語言,使用稱為 Kotlin Native 的語言開發,該語言也使用了 LLVM 編譯機器原生代碼。

LLVM 簡介

LLVM 本質上是一個使用編程方式創建機器原生代碼的軟體庫。開發人員調用其 API,生成一種使用「中間表示」(IR,Intermediate Representation)格式的指令。進而,LLVM 將 IR 編譯為獨立軟體庫,或者使用另一種語言的上下文(例如,使用該語言的編譯器)對代碼執行 JIT(即時,just-in-time)編譯。

LLVM API 提供了一些原語,用於表示開發編程語言中常見結構和模式。例如,幾乎所有的語言都具有函數和全局變數的概念。LLVM 也將函數和全局變數作為 IR 的標準元素。這樣,開發人員可以直接使用 LLVM 的實現,並聚焦於自身語言中的獨到之處,不再需要花費時間和精力去重造這些特定的輪子。

圖 1 一個 LLVM IR 的例子。圖右側顯示了一個使用 C 編寫的簡單程序,左側顯示了使用 Clang 編譯器轉換得到的 LLVM IR 代碼

LLVM:為可移植性而設計

我們通常對 C 語言的認識,可套用到對 LLVM 的認識上。我們時常將 C 語言看成是一種可移植的高層彙編語言,因為 C 中提供了一些直接映射到系統硬體的結構,並已移植到近乎所有現有的系統架構上。但是作為一種可移植的彙編語言並非 C 語言的設計目標,這只是由該語言的工作機制所提供的一個副產品。

與此不同,LLVM IR 的設計從一開始,就是要成為一種可移植的彙編語言。IR 實現可移植性的方式之一,就是提供了獨立於任何特定機器架構的原語。例如,整數類型可使用任何所需的位數,甚至大到 128 位整數,不會受限於機器的最大位寬度。開發人員也無需為匹配某種特定處理器的指令集,考慮如何對輸出做精雕細琢。LLVM 解決了所有這一切。

如果讀者想實地查看 LLVM IR 的運行情況,推薦訪問 ELLCC 項目網站,並可動手在瀏覽器中嘗試一個將 C 代碼轉換為 LLVM IR 的現場演示(文末有鏈接)。

在編程語言中使用 LLVM

LLVM 通常作為語言的 AOT(預先編譯,ahead-of-time)編譯器使用。此外,LLVM 還支持其它一些功能。

使用 LLVM 的 JIT 編譯器

在一些情況下,需要代碼在運行時直接生成,而不是做預先編譯。例如,Julia 語言就對代碼做 JIT 編譯,因為它看重的是運行速度,並可通過 REPL(讀取 - 求值 - 輸出循環,read-eval-print loop)或互動式提示符與用戶交互。.NET 的開源實現 Mono 也提供了選項,支持通過 LLVM 後端方式編譯生成原生代碼。

Python 的高性能科學計算庫 Numba 將設定的 Python 函數 JIT 編譯為機器代碼,也可以對使用了 Numba 的代碼做 AOT 編譯。但是作為一種解釋性語言,Python 與 Julia 一樣也提供了快速開發。使用 JIT 編譯代碼,是對 Python 交互工作流的一種很好的補充,要優於使用 AOT 編譯。

還有一些非正統的方法,也嘗試使用 LLVM 作為 JIT。例如,有方法嘗試編譯 PostgreSQL 查詢,並實現了性能翻五番。

圖 2 Numba 使用 LLVM 對科學計算代碼做 JIT 編譯,加速了代碼的執行。例如,經 JIT 加速的 sum2d 函數, 要比常規 Python 代碼的執行速度快 139 倍

使用 LLVM 做自動代碼優化

LLVM 不僅將 IR 編譯為原生機器代碼,開發人員也可以通過編程方式,指導 LLVM 使用鏈接過程對代碼做高度精細的優化。這種優化卓有成效,其中涉及內聯函數、去除死代碼(包括未使用的類型定義和函數參數)和循環展開(loop unrolling)等。

同樣,LLVM 的強大之處在於無需開發人員自己去實現所有這些功能。LLVM 包攬了所有一切,而且開發人員可在需要時關閉這些功能。例如,如果我們考慮犧牲一些性能去給出更小的二進位文件,可以讓編譯器前端告知 LLVM 禁止循環展開。

使用 LLVM 的領域特定語言(DSL)

通常,LLVM 用於生成通用語言編譯器。但是,LLVM 也可用於生成一些高度垂直或排他性 DSL。我們甚至可以說,這正是 LLVM 大顯身手之處。因為在使用 LLVM 創建一種 DSL 時,無需親歷親為創建語言中的大量苦差事,並可給出良好的表現。

例如,Emscripten 項目使用 LLVM IR,並將 IR 代碼轉化為 JavaScript。這將在理論上支持所有具有 LLVM 後端的語言導出可運行在瀏覽器中的代碼。儘管 Emscripten 的長期計劃是使用基於 LLVM 的後端生成 WebAssembly,但是該項目很好地展示了 LLVM 的靈活性。

另一種使用 LLVM 的方式,是將領域特定的擴展添加到現有的語言中。例如,Nvidia 使用 LLVM 創建了 Nvidia CUDA 編譯器,實現在語言中添加對 CUDA 的原生支持,並作為所生成的原生代碼的一部分做編譯,而不是通過隨之一起交付的軟體庫做調用。

在各種語言中使用 LLVM

LLVM 的通常使用方式,是編碼在開發人員順手的開發語言中。當然,該語言應支持 LLVM 軟體庫。

其中,廣為採用的 C 和 C++。不少 LLVM 開發人員二者必取其一,理由是:

  • LLVM 本事就是使用 C++ 編寫的。

  • LLVM 的 API 以 C/C++ 化身(incarnation)提供。

  • 很多語言開發傾向於以 C/C++ 為基礎。

  • 當然,選擇並不局限於這兩種語言。不少語言支持原生地調用 C 軟體庫。因此在理論上講,可以使用任何一種此類語言做 LLVM 開發。當然,如果語言本身就提供包裝了 LLVM API 的軟體庫,這樣最好。幸運的是,很多語言和運行時都具有這樣的軟體庫,其中包括 C#/.NET/Mono、Rust、Haskell、OCAML、Node.js、Go 和 Python。

    需要給出警告的是,部分語言對 LLVM 的綁定尚不完備。以 Python 為例。儘管 Python 提供了多種選擇,但每種選擇的完備性和實用性各有千秋:

  • LLVM 項目本身就維護了一組到 LLVM C API 的綁定,但是目前為止已停止進一步的維護。

  • llvmpy 在 2015 年後就停止維護了。這對於任何一個軟體項目都不是一個好消息。考慮到每次 LLVM 修訂版本中的更改數量,對於 LLVM 而言尤為如此。

  • llvmlite 是 Numba 開發團隊開發的。當前已成為在 Python 中使用 LLVM 的一個有力競爭者。但是 llvmlite 局限於針對 Numba 的需要,因此提供的功能只是 LLVM 用戶所需功能的一個子集。

  • llvmcpy 意在為 C 軟體庫提供最新的、可自動更新的 Python 綁定,支持使用 Python 的原生風格訪問。llvmcpy 依然處於開發的早期階段,但是已經可以使用 LLVM API 完成一些基本工作。

  • 如果有興趣了解如何使用 LLVM 軟體庫構建一種語言,可以閱讀由 LLVM 創始人撰寫的教程。該教程使用 C++ 和 OCAML,一步步引導讀者去創建一個名為「Kaleidoscope」的簡單語言。進而移植到其它語言中:

  • Haskell:參考原始教程可直接移植。

  • Python: 一種方式是嚴格遵守教程,另一種方式做了大量重寫,並提供了互動式命令行。兩種方式都使用 llvmlite 作為到 LLVM 的綁定。

  • Rust 和 Swift:看上去,我們不可避免地要實現將教程語言移植到這兩種由 LLVM 本身創建的語言上。

  • 該教程還有其它一些國家語言的翻譯版本,例如使用原始 C++ 和 Python 的中文教程。

    LLVM 尚未實現的

    我們上面介紹了 LLVM 提供的很多功能,下面簡述一下它目前尚未實現的。

    例如,LLVM 並不對語法做解析。因為有大量工具可用於完成這個工作,例如 lex/yacc、flex/bison 和 ANTLR。解析必定會從編譯中脫離出來,因此毫不奇怪 LLVM 並未試圖去實現該功能。

    LLVM 也不直接解決大部分針對特定語言的軟體文化。例如,如何安裝編譯器的二進位文件,如何在安裝中管理軟體包,如何升級工具鏈等,這都需要開發人員自己去做。

    最後也是最重要的一點是,LLVM 仍然尚未對部分通用語言成分給出原語。許多語言都具有某種垃圾回收的內存管理方式,或者是作為管理內存的主要方式,或者是作為對 RAII(C ++ 和 Rust 使用)等策略的附屬方式。LLVM 並沒有提供垃圾收集機制,而是提供了一些實現垃圾回收的工具,支持將代碼標記為一些可簡化垃圾收集器編寫的元數據。

    但是,並不排除 LLVM 可能最終會添加實現垃圾回收的本地機制。LLVM 正在以每六個月發布一個主要版本的速度快速發展。鑒於當前許多語言的開發過程是以 LLVM 為中心的,所以 LLVM 的開發速度只可能會進一步提升。

    本文為翻譯文章,原文鏈接:

    https://www.infoworld.com/article/3247799/development-tools/what-is-llvm-the-power-behind-swift-rust-clang-and-more.html

    相關鏈接:


  • Home

  • http://ellcc.org/demo/index.cgi

  • https://llvm.org/docs/GarbageCollection.html

  • 今日薦文

    點擊下方圖片即可閱讀

    eBay 的 Elasticsearch 性能調優實踐


    這裡編輯強烈推薦左耳朵耗子在極客時間 App 上開設的全年專欄,作者將會結合自己 20 年來在大規模系統架構和開發方面的經驗,設置了 2-3 個與分散式系統相關的系列文章。同時,左耳朵耗子學過也用過很多編程語言,所以也會有一系列關於編程本質的文章供大家學習。此外,作者對一些技術知識研究得也比較多,所以還會有一系列與基礎知識相關的文章。其中會穿插一些其他的技術文章,比如一些熱點事件,還有一些經驗之談。這些東西一定會讓你有醍醐灌頂之感。


    推薦閱讀:

    Taylor Swift的鞋子好看又好穿
    Taylor Swift -《泰勒·史薇芙特高清Live!現場合輯》12月30日繼續更新2...
    iOS 開發怎麼入門?
    Swift 為什麼沒有異常處理?

    TAG:語言 | 付出 | 背後 | LLVM | Swift | Swif | 女人 |