LLVM 位元碼檔案格式

摘要

本文描述了 LLVM 位元流檔案格式以及 LLVM IR 如何編碼到其中。

概述

通常所知的 LLVM 位元碼檔案格式(有時也被稱為位元組碼,這是一種過時的說法)實際上是兩個東西:位元流容器格式LLVM IR 的編碼到容器格式中。

位元流格式是一種結構化資料的抽象編碼,在某些方面與 XML 非常相似。與 XML 一樣,位元流檔案包含標籤和巢狀結構,您可以解析檔案而無需理解標籤。與 XML 不同,位元流格式是一種二進制編碼,並且與 XML 不同,它提供了一種機制,讓檔案可以自描述「縮寫」,這實際上是對內容進行大小優化。

LLVM IR 檔案可以選擇性地嵌入到包裝器結構中,或嵌入到原生目標檔案中。這兩種機制都可以輕鬆地將額外數據與 LLVM IR 檔案一起嵌入。

本文件首先描述 LLVM 位元流格式,然後描述包裝器格式,最後描述 LLVM IR 檔案使用的記錄結構。

位元流格式

位元流格式實際上就是位元流,結構非常簡單。此結構包含以下概念

  • 一個用於識別流內容的「魔數」。

  • 編碼基本類型,例如可變位元率整數。

  • 區塊,用於定義嵌套內容。

  • 資料記錄,用於描述檔案中的實體。

  • 縮寫,用於指定檔案的壓縮最佳化。

請注意,llvm-bcanalyzer 工具可用於傾印和檢查任意位元流,這對於理解編碼非常有用。

魔數

位元流的前四個位元組用作應用程式特定的魔數。通用的位元碼工具可能會查看前四個位元組,以確定流是否是已知的流類型。但是,這些工具*不應該*僅根據魔數來判斷位元流是否有效。新的應用程式特定的位元流格式一直在開發中;工具不應僅僅因為它們具有迄今為止從未見過的魔數就拒絕它們。

基本類型

位元流實際上是由一系列位元組成的,這些位元從每個位元組的最低有效位元開始按順序讀取。流由許多基本值組成,這些基本值編碼了一系列無符號整數值。這些整數以兩種方式編碼:固定寬度整數可變寬度整數

固定寬度整數

固定寬度整數值的低位元直接發送到檔案中。例如,一個 3 位元的整數值將 1 編碼為 001。當欄位選項數量已知時,就會使用固定寬度整數。例如,布林值通常使用 1 位元寬的整數進行編碼。

可變寬度整數

可變寬度整數 (VBR) 值可以編碼任意大小的值,並針對值較小的情況進行了最佳化。給定一個 4 位元的 VBR 欄位,任何 3 位元的值(0 到 7)都直接編碼,最高位元設定為零。大於 N-1 位元的值將以一系列 N-1 位元的區塊發送其位元,其中除了最後一個區塊之外,所有區塊的最高位元都設定為 1。

例如,值 30 (0x1E) 在作為 vbr4 值發送時,會被編碼為 62 (0b0011'1110)。從最低有效位元開始的第一組四個位元表示值 6 (110),並帶有一個延續部分(由最高位元 1 表示)。下一組四個位元表示值 24 (011 << 3),沒有延續部分。總和 (6+24) 產生值 30。

6 位元字元

6 位元字元將常用字元編碼為固定的 6 位元欄位。它們使用以下 6 位元值表示以下字元

'a' .. 'z' ---  0 .. 25
'A' .. 'Z' --- 26 .. 51
'0' .. '9' --- 52 .. 61
       '.' --- 62
       '_' --- 63

此編碼僅適用於編碼僅包含上述字元的字元和字串。 它完全無法編碼不在集合中的字元。

詞組對齊

有時,發出零位元直到位元流是 32 位元的倍數是很有用的。 這確保了流中的位元位置可以表示為 32 位元字組的倍數。

縮寫 ID

位元流是 區塊資料記錄 的序列。 這兩者都以編碼為固定位元寬度欄位的縮寫 ID 開始。 寬度由當前區塊指定,如下所述。 縮寫 ID 的值指定內建 ID(具有特殊含義,如下定義)或由流本身為當前區塊定義的縮寫 ID 之一。

內建縮寫 ID 的集合是

縮寫 ID 4 及以上由流本身定義,並指定 縮寫記錄編碼

區塊

位元流中的區塊表示流的嵌套區域,並由特定於內容的 ID 號碼標識(例如,LLVM IR 使用 ID 12 來表示函數主體)。 區塊 ID 0-7 保留用於 標準區塊,其含義由位元碼定義; 區塊 ID 8 及更大是應用程序特定的。 嵌套區塊捕獲其中編碼的數據的層次結構,並且在解析文件時,各種屬性都與區塊相關聯。 區塊定義允許讀取器在需要區塊摘要或想要有效跳過它不理解的數據時,在恆定時間內有效跳過區塊。 LLVM IR 讀取器使用此機制跳過函數主體,並根據需要延遲讀取它們。

在讀取和編碼流時,會為該區塊維護幾個屬性。 特別是,每個區塊都維護

  1. 當前縮寫 ID 寬度。 該值在流開始時從 2 開始,並且每次輸入區塊記錄時都會設置。 區塊條目指定區塊主體的縮寫 ID 寬度。

  2. 一組縮寫。 縮寫可以在區塊內定義,在這種情況下,它們僅在該區塊中定義(子區塊和封閉區塊都看不到縮寫)。 縮寫也可以在 BLOCKINFO 區塊內定義,在這種情況下,它們在與 BLOCKINFO 區塊描述的 ID 匹配的所有區塊中定義。

當輸入子區塊時,會保存這些屬性,並且新的子區塊有自己的縮寫集和自己的縮寫 ID 寬度。 當彈出子區塊時,將恢復保存的值。

ENTER_SUBBLOCK 編碼

[ENTER_SUBBLOCK, blockidvbr8, newabbrevlenvbr4, <align32bits>, blocklen_32]

縮寫 ID ENTER_SUBBLOCK 指定一個新的區塊記錄的開始。blockid 值編碼為 8 位元 VBR 標識符,並指示正在輸入的區塊類型,可以是標準區塊或應用程序特定區塊。newabbrevlen 值是一個 4 位元 VBR,它指定子區塊的縮寫 ID 寬度。blocklen 值是一個 32 位元對齊的值,指定子區塊的大小(以 32 位元字為單位)。此值允許讀取器一次跳過整個區塊。

END_BLOCK 編碼

[END_BLOCK, <align32bits>]

縮寫 ID END_BLOCK 指定當前區塊記錄的結束。其結尾與 32 位元對齊,以確保區塊的大小是 32 位元的偶數倍。

數據記錄

數據記錄由一個記錄代碼和多個(最多)64 位元整數值組成。代碼和值的解釋是特定於應用程序的,並且可能在不同的區塊類型之間有所不同。記錄可以使用未縮寫的記錄或縮寫進行編碼。例如,在 LLVM IR 格式中,有一個記錄編碼模組的目標三元組。代碼是 MODULE_CODE_TRIPLE,記錄的值是字符串中字符的 ASCII 代碼。

UNABBREV_RECORD 編碼

[UNABBREV_RECORD, codevbr6, numopsvbr6, op0vbr6, op1vbr6, …]

UNABBREV_RECORD 提供了一個默認的回退編碼,它既完全通用又非常低效。它可以通過將代碼和操作數發射為 VBR 來描述任意記錄。

例如,將 LLVM IR 目標三元組作為未縮寫的記錄發射需要發射 UNABBREV_RECORD 縮寫 ID、MODULE_CODE_TRIPLE 代碼的 vbr6、字符串長度的 vbr6(等於操作數的數量)以及每個字符的 vbr6。因為沒有值小於 32 的字母,所以每個字母都需要至少發射為兩部分 VBR,這意味著每個字母至少需要 12 位元。這不是一種有效的編碼,但它是完全通用的。

縮寫記錄編碼

[<abbrevid>, fields...]

縮寫記錄是一個縮寫 ID,後跟一組根據縮寫定義編碼的字段。這允許記錄比使用 UNABBREV_RECORD 類型編碼的記錄更密集地編碼,並允許在流本身中指定縮寫類型,這允許文件完全自描述。縮寫的實際編碼定義如下。

記錄代碼是縮寫記錄的第一個字段,可以在縮寫定義中編碼(作為文字操作數)或在縮寫記錄中提供(作為固定或 VBR 操作數值)。

縮寫

縮寫是壓縮位元流的重要形式。 其概念是為一類記錄指定一種密集的編碼,然後使用該編碼來發出許多記錄。 將編碼發送到檔案中需要空間,但是當發送使用該編碼的記錄時,空間會被回收(希望還有一些)。

可以針對每個用戶端、每個檔案動態確定縮寫。 由於縮寫存儲在位元流本身中,因此同一格式的不同流可以根據特定流的需求包含不同的縮寫集。 作為一個具體的例子,LLVM IR 檔案通常會為二元運算符發出縮寫。 如果特定的 LLVM 模組不包含或只包含很少的二元運算符,則不需要發出縮寫。

DEFINE_ABBREV 編碼

[DEFINE_ABBREV, numabbrevopsvbr5, abbrevop0, abbrevop1, …]

`DEFINE_ABBREV` 記錄會將縮寫添加到此區塊範圍內當前定義的縮寫列表中。 此定義僅存在於此直接區塊內 - 在子區塊或封閉區塊中不可見。 縮寫從 4(第一個應用程式定義的縮寫 ID)開始按順序隱式分配 ID。 為特定區塊類型在 `BLOCKINFO` 記錄中定義的任何縮寫都按順序接收 ID,然後是區塊本身內定義的任何縮寫。 縮寫資料記錄引用此 ID 以指示它們正在呼叫哪個縮寫。

縮寫定義由 `DEFINE_ABBREV` 縮寫 ID 後跟一個 VBR 組成,該 VBR 指定縮寫運算元的數量,然後是縮寫運算元本身。 縮寫運算元有三種形式。 它們都以一個位元開始,該位元指示縮寫運算元是字面運算元(當位元為 1 時)還是編碼運算元(當位元為 0 時)。

  1. 字面運算元 — [11, litvaluevbr8] — 字面運算元指定結果中的值始終是單個特定值。 此特定值在指示它是字面運算元的位元之後作為 vbr8 發出。

  2. 沒有資料的編碼資訊 — [01, encoding3] — 沒有額外資料的運算元編碼僅作為其程式碼發出。

  3. 帶有資料的編碼資訊 — [01, encoding3, valuevbr5] — 具有額外資料的運算元編碼作為其程式碼發出,後跟額外資料。

可能的運算元編碼是

  • 固定(程式碼 1):該欄位應作為 固定寬度值 發出,其寬度由運算元的額外資料指定。

  • VBR(程式碼 2):該欄位應作為 可變寬度值 發出,其寬度由運算元的額外資料指定。

  • 陣列(程式碼 3):此欄位是一個值陣列。 陣列運算元沒有額外資料,但預計後面會跟著另一個運算元,指示陣列的元素類型。 在縮寫記錄中讀取陣列時,第一個整數是一個 vbr6,它指示陣列長度,後跟陣列的編碼元素。 陣列只能作為縮寫的最後一個運算元出現(給出陣列類型的最後一個運算元除外)。

  • Char6(程式碼 4):此欄位應作為 char6 編碼值 發出。 此運算元類型不採用額外資料。 Char6 編碼通常用作陣列元素類型。

  • Blob(代碼 5):此欄位以 vbr6 的形式發出,後面接著填補至 32 位元邊界(為了對齊)和一個 8 位元物件陣列。該位元組陣列後面接著尾部填補,以確保其總長度是 4 位元組的倍數。這使得讀取器可以非常有效地解碼資料,而無需建立副本:它可以使用指向映射檔案中資料的指標,並直接訪問它。 Blob 可能只會作為縮寫的最後一個運算元出現。

舉例來說,LLVM 模組中的目標三元組會被編碼為以下形式的記錄:[TRIPLE, 'a', 'b', 'c', 'd']。考慮如果位元流發出以下縮寫條目

[0, Fixed, 4]
[0, Array]
[0, Char6]

當使用此縮寫發出記錄時,上述條目將發出為

[4縮寫寬度, 24, 4vbr6, 06, 16, 26, 36]

這些值為

  1. 第一個值 4 是此縮寫的縮寫 ID。

  2. 第二個值 2 是 LLVM IR 檔案 MODULE_BLOCK 區塊中 TRIPLE 記錄的記錄代碼。

  3. 第三個值 4 是陣列的長度。

  4. 其餘值為 "abcd" 的 char6 編碼值。

使用此縮寫,目標三元組僅使用 37 個位元發出(假設縮寫 ID 寬度為 3)。如果沒有縮寫,則需要更多空間來發出目標三元組。此外,由於 TRIPLE 值並未在縮寫中發出為常值,因此該縮寫也可以用於任何其他字串值。

標準區塊

除了基本的區塊結構和記錄編碼之外,位元流還定義了特定的內建區塊類型。這些區塊類型指定了如何解碼流或其他中繼資料。未來可能會新增新的標準區塊。區塊 ID 0-7 保留給標準區塊使用。

#0 - BLOCKINFO 區塊

BLOCKINFO 區塊允許描述其他區塊的中繼資料。目前指定的記錄為

[SETBID (#1), blockid]
[DEFINE_ABBREV, ...]
[BLOCKNAME, ...name...]
[SETRECORDNAME, RecordID, ...name...]

SETBID 記錄(代碼 1)表示正在描述哪個區塊 ID。SETBID 記錄可以在整個區塊中出現多次,以更改正在描述的區塊 ID。在任何其他記錄之前,必須有一個 SETBID 記錄。

標準 DEFINE_ABBREV 記錄可以出現在 BLOCKINFO 區塊內,但與它們在一般區塊中的出現不同,縮寫是針對與我們正在描述的區塊 ID 相符的區塊定義的,而_不是_ BLOCKINFO 區塊本身。在 BLOCKINFO 區塊中定義的縮寫會收到如 DEFINE_ABBREV 中所述的縮寫 ID。

BLOCKNAME 記錄(代碼 2)可以選擇性地出現在此區塊中。記錄的元素是區塊字串名稱的位元組。 llvm-bcanalyzer 可以使用它來符號化地傾印出位元碼檔案。

SETRECORDNAME 記錄(代碼 3)也可以選擇性地出現在此區塊中。第一個運算元值是記錄 ID 號碼,記錄的其餘元素是記錄字串名稱的位元組。 llvm-bcanalyzer 可以使用它來符號化地傾印出位元碼檔案。

請注意,雖然 BLOCKINFO 區塊中的數據被描述為「中繼資料」,但它們包含的縮寫對於解析來自相應區塊的記錄至關重要。跳過它們是不安全的。

位元碼包裝格式

LLVM IR 的位元碼文件可以選擇性地包裝在一個簡單的包裝結構中。此結構包含一個簡單的標頭,指示嵌入式 BC 文件的偏移量和大小。這允許將額外信息與 BC 文件一起存儲。此文件標頭的結構為

[Magic32, Version32, Offset32, Size32, CPUType32]

每個字段都是以小端序存儲的 32 位字段(與位元碼文件的其餘字段相同)。魔數始終為 0x0B17C0DE,版本當前始終為 0。Offset 字段是文件中位元碼流開始處的偏移量(以字節為單位),Size 字段是流的大小(以字節為單位)。CPUType 是一個特定於目標的值,可用於對目標的 CPU 進行編碼。

原生對象文件包裝格式

LLVM IR 的位元碼文件也可以包裝在本機對象文件(即 ELF、COFF、Mach-O)中。位元碼必須存儲在對象文件的名為 __LLVM,__bitcode(對於 MachO)或 .llvmbc(對於其他對象格式)的部分中。ELF 對象還支持用於 FatLTO.llvm.lto 部分,其中包含適用於 LTO 編譯的位元碼(即已通過預鏈接 LTO 管道的位元碼)。.llvmbc 部分早於 LLVM 中對 FatLTO 的支持,並且可能並不總是包含適用於 LTO 的位元碼(例如來自 -fembed-bitcode)。包裝格式適用於在必須是本機對象文件的編譯管道中容納 LTO,這些對象文件包含其他部分中的中繼數據。

並非所有工具都支持此格式。例如,lld 和 gold 插件在鏈接對象文件時會忽略 .llvmbc 部分,但在傳遞正確的命令行選項時可以使用 .llvm.lto 部分。

LLVM IR 編碼

LLVM IR 通過定義區塊和記錄被編碼到位元流中。它使用區塊來處理常量池、函數、符號表等內容。它使用記錄來處理指令、全局變量描述符、類型描述等內容。本文檔沒有描述編寫器使用的縮寫集,因為這些縮寫在文件中是完全自描述的,並且不允許讀者了解這些縮寫。

基礎知識

LLVM IR 魔數

LLVM IR 文件的魔數為

[‘B’8, ‘C’8, 0x04, 0xC4, 0xE4, 0xD4]

帶符號 VBR

可變寬度整數 編碼是一種對任意大小的無符號值進行編碼的有效方法,但對於編碼有符號值效率極低,因為有符號值會被視為最大無符號值。

因此,特定寬度的有符號 VBR 值的發出方式如下

  • 正值以指定寬度的 VBR 發出,但其值會向左移一位。

  • 負值以指定寬度的 VBR 發出,但取反的值會向左移一位,並將最低有效位設定為 1。

使用這種編碼,小的正值和小的負值都可以有效地發出。有符號 VBR 編碼用於 CONSTANTS_BLOCK 區塊中的 CST_CODE_INTEGERCST_CODE_WIDE_INTEGER 記錄中。它也用於 MODULE_CODE_VERSION 1 中的 phi 指令操作數。

LLVM IR 區塊

LLVM IR 使用以下區塊定義

MODULE_BLOCK 內容

MODULE_BLOCK 區塊(id 8)是 LLVM 位元碼檔案的最上層區塊,位元碼檔案中的每個模組都必須包含一個。具有多模組位元碼的位元碼檔案是有效的。除了包含模組資訊的記錄(如下所述)之外,MODULE_BLOCK 區塊還可以包含以下子區塊

MODULE_CODE_VERSION 記錄

[VERSION, version#]

VERSION 記錄(代碼 1)包含一個值,指示格式版本。目前支援版本 0、1 和 2。版本 0 和 1 之間的區別在於每個 FUNCTION_BLOCK 中指令操作數的編碼。

在版本 0 中,指令定義的每個值都會被分配一個在函式中唯一的 ID。函式層級的值 ID 從 NumModuleValues 開始分配,因為它們與模組層級的值共用同一個命名空間。值列舉器在每個函式後重置。當一個值是指令的操作數時,將使用值 ID 來表示該操作數。對於大型函式或大型模組,這些操作數值可能會很大。

版本 1 中的編碼嘗試避免在常見情況下出現大型操作數值。操作數不是直接使用值 ID,而是編碼為相對於當前指令的相對值。因此,如果操作數是由前一條指令定義的值,則該操作數將被編碼為 1。

例如,以下列程式碼為例:

#n = load #n-1
#n+1 = icmp eq #n, #const0
br #n+1, label #(bb1), label #(bb2)

版本 1 會將指令編碼為:

#n = load #1
#n+1 = icmp eq #1, (#n+1)-#const0
br #1, label #(bb1), label #(bb2)

請注意,在範例中,作為常數的操作數也使用相對編碼,而基本區塊標籤等操作數則不使用相對編碼。

前向參考將導致負值。這可能效率低下,因為操作數通常編碼為無符號 VBR。但是,前向參考很少見,除了 phi 指令的情況。對於 phi 指令,操作數編碼為 帶符號 VBR 以處理前向參考。

在版本 2 中,模組記錄 FUNCTIONGLOBALVARALIASIFUNCCOMDAT 的含義發生了變化,前兩個操作數指定字串表中字串的偏移量和大小(請參閱 STRTAB_BLOCK 內容),函式名稱從值符號表中的 FNENTRY 記錄中移除,並且頂級 VALUE_SYMTAB_BLOCK 可能只包含 FNENTRY 記錄。

MODULE_CODE_TRIPLE 記錄

[TRIPLE, ...字串...]

TRIPLE 記錄(代碼 2)包含表示 目標 三元組 規範字串位元組的可變數量的值。

MODULE_CODE_DATALAYOUT 記錄

[DATALAYOUT, ...字串...]

DATALAYOUT 記錄(代碼 3)包含表示 目標 資料佈局 規範字串位元組的可變數量的值。

MODULE_CODE_ASM 記錄

[ASM, ...字串...]

ASM 記錄(代碼 4)包含表示 模組 asm 字串位元組的可變數量的值,各個組譯區塊之間以換行符號(ASCII 10)分隔。

MODULE_CODE_SECTIONNAME 記錄

[SECTIONNAME, ...字串...]

SECTIONNAME 記錄(代碼 5)包含表示單個區段名稱字串之位元組的可變數量的值。模組中每個被引用的區段名稱(例如,在全域變數或函數 section 屬性中)都應該有一個 SECTIONNAME 記錄。這些記錄可以通過 GLOBALVARFUNCTION 記錄的 *section* 欄位中的基於 1 的索引來引用。

MODULE_CODE_DEPLIB 記錄

[DEPLIB, ...字串...]

DEPLIB 記錄(代碼 6)包含表示單個依賴庫名稱字串(deplibs 聲明中提到的庫之一)之位元組的可變數量的值。每個被引用的庫名稱都應該有一個 DEPLIB 記錄。

MODULE_CODE_GLOBALVAR 記錄

[GLOBALVAR, 字串表 偏移量, 字串表 大小, 指標 類型, 是否為常數, 初始值 ID, 連結類型, 對齊方式, 區段, 可見性, 執行緒區域, 未命名地址, 外部初始化, dll 儲存類別, comdat, 屬性, 搶佔說明符]

GLOBALVAR 記錄(代碼 7)標記全域變數的聲明或定義。操作數欄位為

  • *字串表偏移量*、*字串表大小*:指定全域變數的名稱。請參閱 STRTAB_BLOCK 內容

  • *指標類型*:用於指向此全域變數的指標類型的類型索引

  • *是否為常數*:如果變數在模組中被視為常數,則為非零值;否則為零。

  • *初始值 ID*:如果為非零值,則為此變數的初始值器的值索引加 1。

  • *連結類型*:此變數的連結類型編碼

    • external:代碼 0

    • weak:代碼 1

    • appending:代碼 2

    • internal:代碼 3

    • linkonce:代碼 4

    • dllimport:代碼 5

    • dllexport:代碼 6

    • extern_weak:代碼 7

    • common:代碼 8

    • private:代碼 9

    • weak_odr:代碼 10

    • linkonce_odr:代碼 11

    • available_externally:代碼 12

    • 已棄用:代碼 13

    • 已棄用:代碼 14

  • *對齊方式*:變數請求的對齊方式的以 2 為底的對數加 1

  • *區段*:如果為非零值,則為 MODULE_CODE_SECTIONNAME 條目表中的基於 1 的區段索引。

  • 可視性:如果存在,則為此變數可視性的編碼。

    • default:代碼 0

    • hidden:代碼 1

    • protected:代碼 2

  • 執行緒區域儲存區:如果存在,則為此變數執行緒區域儲存區模式的編碼。

    • not thread local:代碼 0

    • thread local; default TLS model:代碼 1

    • localdynamic:代碼 2

    • initialexec:代碼 3

    • localexec:代碼 4

  • unnamed_addr:如果存在,則為此變數 unnamed_addr 屬性的編碼。

    • not unnamed_addr:代碼 0

    • unnamed_addr:代碼 1

    • local_unnamed_addr:代碼 2

  • dllstorageclass:如果存在,則為此變數 DLL 儲存類別的編碼。

    • default:代碼 0

    • dllimport:代碼 1

    • dllexport:代碼 2

  • comdat:此函數的 COMDAT 編碼。

  • 屬性:如果不為零,則為 AttributeLists 表格中以 1 為基底的索引。

  • preemptionspecifier:如果存在,則為此變數的執行階段搶佔說明符的編碼。

    • dso_preemptable:代碼 0

    • dso_local:代碼 1

MODULE_CODE_FUNCTION 記錄

[FUNCTION, strtab 偏移量, strtab 大小, 類型, 呼叫慣例, 是否為原型, 連結, 參數屬性, 對齊, 區段, 可視性, gc, prologuedata, dllstorageclass, comdat, prefixdata, personalityfn, preemptionspecifier]

FUNCTION 記錄(代碼 8)標記函數的宣告或定義。運算元欄位為:

  • strtab 偏移量strtab 大小:指定函數的名稱。請參閱 STRTAB_BLOCK 內容

  • 類型:描述此函數的函數類型的類型索引。

  • callingconv:呼叫慣例編號:* ccc:代碼 0 * fastcc:代碼 8 * coldcc:代碼 9 * anyregcc:代碼 13 * preserve_mostcc:代碼 14 * preserve_allcc:代碼 15 * swiftcc:代碼 16 * cxx_fast_tlscc:代碼 17 * tailcc:代碼 18 * cfguard_checkcc:代碼 19 * swifttailcc:代碼 20 * x86_stdcallcc:代碼 64 * x86_fastcallcc:代碼 65 * arm_apcscc:代碼 66 * arm_aapcscc:代碼 67 * arm_aapcs_vfpcc:代碼 68

  • isproto*:如果此項目表示宣告而不是定義,則為非零值

  • linkage:此函式的 連結類型 編碼

  • paramattr:如果不為零,則為 PARAMATTR_CODE_ENTRY 項目表格中以 1 為基底的參數屬性索引。

  • alignment:函式要求對齊的以 2 為底的對數,加 1

  • *區段*:如果為非零值,則為 MODULE_CODE_SECTIONNAME 條目表中的基於 1 的區段索引。

  • visibility:此函式的 可見性 編碼

  • gc:如果存在且非零,則為 MODULE_CODE_GCNAME 項目表格中以 1 為基底的垃圾收集器索引。

  • unnamed_addr:如果存在,則為此函式的 unnamed_addr 屬性編碼

  • prologuedata:如果不為零,則為此函式的序言資料值索引,加 1。

  • dllstorageclass:此函式的 dllstorageclass 編碼

  • comdat:此函數的 COMDAT 編碼。

  • prefixdata:如果不為零,則為此函式的字首資料值索引,加 1。

  • personalityfn:如果不為零,則為此函式的個性化函式值索引,加 1。

  • preemptionspecifier:如果存在,則為此函式的 執行階段搶佔規範 編碼。

MODULE_CODE_ALIAS 記錄

[ALIAS, strtab 偏移量, strtab 大小, 別名 類型, 別名目標 值#, 連結, 可見性, dllstorageclass, threadlocal, unnamed_addr, preemptionspecifier]

ALIAS 記錄(代碼 9)標記別名的定義。運算元欄位為

  • strtab 偏移量strtab 大小:指定別名的名稱。請參閱 STRTAB_BLOCK 內容

  • 別名類型:別名的類型索引

  • 別名目標值#:別名目標的值索引

  • 連結:此別名的 連結類型 編碼

  • 可見性:如果存在,則為別名的 可見性 編碼

  • dllstorageclass:如果存在,表示別名的 dllstorageclass 編碼

  • threadlocal:如果存在,表示別名的 線程局部屬性 編碼

  • unnamed_addr:如果存在,表示此別名的 unnamed_addr 屬性編碼

  • preemptionspecifier:如果存在,表示此別名的 運行時搶佔說明符 編碼。

MODULE_CODE_GCNAME 記錄

[GCNAME, ...字符串...]

GCNAME 記錄(代碼 11)包含表示單個垃圾回收器名稱字符串的字節的可變數量值。模組中函數 gc 屬性引用的每個垃圾回收器名稱都應該有一個 GCNAME 記錄。這些記錄可以在 FUNCTION 記錄的 gc 字段中通過從 1 開始的索引進行引用。

PARAMATTR_BLOCK 內容

PARAMATTR_BLOCK 區塊(id 9)包含一個描述函數參數屬性的條目表。這些條目在模組區塊 FUNCTION 記錄的 paramattr 字段中,或在函數區塊 INST_INVOKEINST_CALL 記錄的 attr 字段中通過從 1 開始的索引進行引用。

PARAMATTR_BLOCK 中的條目經過構造以確保每個條目都是唯一的(即,沒有兩個索引表示等效的屬性列表)。

PARAMATTR_CODE_ENTRY 記錄

[ENTRY, attrgrp0, attrgrp1, ...]

ENTRY 記錄(代碼 2)包含描述一組唯一函數參數屬性的可變數量值。每個 attrgrp 值都用作鍵,用於在 PARAMATTR_GROUP_BLOCK 區塊中描述的屬性組表中查找條目。

PARAMATTR_CODE_ENTRY_OLD 記錄

備註

這是 LLVM 3.2 及更早版本產生的舊版屬性編碼。根據 IR 向後兼容性 策略中的規定,當前 LLVM 版本保證可以理解它。

[ENTRY, paramidx0, attr0, paramidx1, attr1...]

ENTRY 記錄(代碼 1)包含偶數個值,用於描述一組唯一的函數參數屬性。每個 paramidx 值表示表示哪一組屬性,0 表示返回值屬性,0xFFFFFFFF 表示函數屬性,其他值表示從 1 開始的函數參數。每個 attr 值都是一個位圖,具有以下解釋

  • 位 0:zeroext

  • 位 1:signext

  • 位 2:noreturn

  • 位 3:inreg

  • 位 4:sret

  • 位元 5:nounwind

  • 位元 6:noalias

  • 位元 7:byval

  • 位元 8:nest

  • 位元 9:readnone

  • 位元 10:readonly

  • 位元 11:noinline

  • 位元 12:alwaysinline

  • 位元 13:optsize

  • 位元 14:ssp

  • 位元 15:sspreq

  • 位元 16-31:align n

  • 位元 32:nocapture

  • 位元 33:noredzone

  • 位元 34:noimplicitfloat

  • 位元 35:naked

  • 位元 36:inlinehint

  • 位元 37-39:alignstack n,以 2 為底數的對數表示請求的對齊,加 1

PARAMATTR_GROUP_BLOCK 內容

PARAMATTR_GROUP_BLOCK 區塊(ID 10)包含一個表格,其中包含描述模組中存在的屬性群組的項目。這些項目可以在 PARAMATTR_CODE_ENTRY 項目中被引用。

PARAMATTR_GRP_CODE_ENTRY 記錄

[ENTRY, grpid, paramidx, attr0, attr1, ...]

ENTRY 記錄(代碼 3)包含 grpidparamidx 值,後跟描述唯一屬性群組的可變數量的值。grpid 值是屬性群組的唯一鍵,可以在 PARAMATTR_CODE_ENTRY 項目中被引用。paramidx 值指示表示哪一組屬性,0 表示返回值屬性,0xFFFFFFFF 表示函數屬性,其他值表示從 1 開始的函數參數。

每個 attr 本身都由可變數量的值表示

kind, key [, ...], [value [, ...]]

每個屬性都是一個已知的 LLVM 屬性(可能帶有一個與之關聯的整數值),或者是一個任意字符串(可能帶有一個與之關聯的任意字符串值)。kind 值是一個整數代碼,用於區分這些可能性

  • 代碼 0:已知屬性

  • 代碼 1:帶有整數值的已知屬性

  • 代碼 3:字符串屬性

  • 代碼 4:帶有字符串值的字符串屬性

對於已知屬性(代碼 0 或 1),key 值是一個標識屬性的整數代碼。對於帶有整數參數的屬性(代碼 1),value 值表示參數。

對於字串屬性(代碼 3 或 4),值實際上是表示以空值終止的字串之位元組的可變數值。對於具有字串引數的屬性(代碼 4),值同樣是表示以空值終止的字串之位元組的可變數值。

整數代碼會對應至屬性,如檔案 LLVMBitCodes.h 中的 AttributeKindCodes 列舉所示。

舉例來說

enum AttributeKindCodes {
  // = 0 is unused
  ATTR_KIND_ALIGNMENT = 1,
  ATTR_KIND_ALWAYS_INLINE = 2,
  ...
  }

對應到

  • 代碼 1:align(<n>)

  • 代碼 2:alwaysinline

列舉與屬性名稱字串之間的對應關係可以在檔案 Attributes.td 中找到。

備註

allocsize 屬性具有特殊的引數編碼方式。它的兩個引數是 32 位元整數,會被封裝到一個 64 位元整數值中(即 (EltSizeParam << 32) | NumEltsParam),其中如果未指定 NumEltsParam,則會採用哨兵值 -1。

備註

vscale_range 屬性具有特殊的引數編碼方式。它的兩個引數是 32 位元整數,會被封裝到一個 64 位元整數值中(即 (Min << 32) | Max),其中如果未指定 Max,則會採用 Min 的值。

TYPE_BLOCK 內容

TYPE_BLOCK 區塊(id 17)包含構成類型運算符條目表格的記錄,用於表示 LLVM 模組中引用的類型。每個記錄(NUMENTRY 除外)都會產生一個類型表格條目,可以透過指令、常數、中繼資料、類型符號表格條目或其他類型運算符記錄中的從 0 開始的索引來引用。

TYPE_BLOCK 中的條目會進行建構,以確保每個條目都是唯一的(即,沒有兩個索引表示結構上等效的類型)。

TYPE_CODE_NUMENTRY 記錄

[NUMENTRY, numentries]

NUMENTRY 記錄(代碼 1)包含一個值,該值指示模組類型表格中的類型代碼條目總數。如果存在,則 NUMENTRY 應該是區塊中的第一個記錄。

TYPE_CODE_VOID 記錄

[VOID]

VOID 記錄(代碼 2)將 void 類型添加到類型表格中。

TYPE_CODE_HALF 記錄

[HALF]

HALF 記錄(代碼 10)將 half(16 位元浮點數)類型添加到類型表格中。

TYPE_CODE_BFLOAT 記錄

[BFLOAT]

BFLOAT 記錄(代碼 23)將 bfloat(16 位元腦浮點數)類型添加到類型表中。

TYPE_CODE_FLOAT 記錄

[FLOAT]

FLOAT 記錄(代碼 3)將 float(32 位元浮點數)類型添加到類型表中。

TYPE_CODE_DOUBLE 記錄

[DOUBLE]

DOUBLE 記錄(代碼 4)將 double(64 位元浮點數)類型添加到類型表中。

TYPE_CODE_LABEL 記錄

[LABEL]

LABEL 記錄(代碼 5)將 label 類型添加到類型表中。

TYPE_CODE_OPAQUE 記錄

[OPAQUE]

OPAQUE 記錄(代碼 6)將一個 opaque 類型添加到類型表中,其名稱由先前遇到的 STRUCT_NAME 記錄定義。請注意,不同的 opaque 類型不會被統一。

TYPE_CODE_INTEGER 記錄

[INTEGER, 寬度]

INTEGER 記錄(代碼 7)將一個整數類型添加到類型表中。單個的 *寬度* 欄位表示整數類型的寬度。

TYPE_CODE_POINTER 記錄

[POINTER, 指標指向類型, 位址空間]

POINTER 記錄(代碼 8)將一個指標類型添加到類型表中。運算元欄位為

  • *指標指向類型*:指標指向類型的類型索引

  • *位址空間*:如果提供,則為指標指向物件所在的目標特定編號位址空間。否則,預設位址空間為零。

TYPE_CODE_FUNCTION_OLD 記錄

備註

這是函數的舊版編碼,由 LLVM 3.0 及更早版本產生。根據 IR 向後相容性 政策,當前 LLVM 版本保證可以理解它。

[FUNCTION_OLD, vararg, 已忽略, retty, ...paramty... ]

FUNCTION_OLD 記錄(代碼 9)將一個函數類型添加到類型表中。運算元欄位為

  • *vararg*:如果該類型代表一個可變參數函數,則為非零值

  • *已忽略*:此值欄位僅出於向後相容性的目的而存在,並且會被忽略

  • retty:函數回傳類型的類型索引。

  • paramty:零個或多個類型索引,表示函數的參數類型。

TYPE_CODE_ARRAY 記錄

[ARRAY, numelts, eltty]

ARRAY 記錄(代碼 11)將陣列類型添加到類型表中。操作數欄位為:

  • numelts:此類型的陣列中的元素數量。

  • eltty:陣列元素類型的類型索引。

TYPE_CODE_VECTOR 記錄

[VECTOR, numelts, eltty]

VECTOR 記錄(代碼 12)將向量類型添加到類型表中。操作數欄位為:

  • numelts:此類型的向量中的元素數量。

  • eltty:向量元素類型的類型索引。

TYPE_CODE_X86_FP80 記錄

[X86_FP80]

X86_FP80 記錄(代碼 13)將 x86_fp80(80 位元浮點數)類型添加到類型表中。

TYPE_CODE_FP128 記錄

[FP128]

FP128 記錄(代碼 14)將 fp128(128 位元浮點數)類型添加到類型表中。

TYPE_CODE_PPC_FP128 記錄

[PPC_FP128]

PPC_FP128 記錄(代碼 15)將 ppc_fp128(128 位元浮點數)類型添加到類型表中。

TYPE_CODE_METADATA 記錄

[METADATA]

METADATA 記錄(代碼 16)將 metadata 類型添加到類型表中。

TYPE_CODE_X86_MMX 記錄

[X86_MMX]

X86_MMX 記錄(代碼 17)已棄用,並作為 <1 x i64> 向量匯入。

TYPE_CODE_STRUCT_ANON 記錄

[STRUCT_ANON, ispacked, ...eltty...]

STRUCT_ANON 記錄(代碼 18)將文字結構類型添加到類型表中。操作數欄位為:

  • ispacked:如果類型表示緊湊結構,則為非零。

  • eltty:零個或多個類型索引,表示結構的元素類型。

TYPE_CODE_STRUCT_NAME 記錄

[STRUCT_NAME, ...string...]

STRUCT_NAME 記錄(代碼 19)包含可變數量的值,表示結構名稱的位元組。下一個 OPAQUESTRUCT_NAMED 記錄將使用此名稱。

TYPE_CODE_STRUCT_NAMED 記錄

[STRUCT_NAMED, ispacked, ...eltty...]

STRUCT_NAMED 記錄(代碼 20)會將一個具名結構類型添加到類型表中,其名稱由先前遇到的 STRUCT_NAME 記錄定義。操作數欄位為

  • ispacked:如果類型表示緊湊結構,則為非零。

  • eltty:零個或多個類型索引,表示結構的元素類型。

TYPE_CODE_FUNCTION 記錄

[FUNCTION, vararg, retty, ...paramty... ]

FUNCTION 記錄(代碼 21)會將一個函數類型添加到類型表中。操作數欄位為

  • *vararg*:如果該類型代表一個可變參數函數,則為非零值

  • retty:函數回傳類型的類型索引。

  • paramty:零個或多個類型索引,表示函數的參數類型。

TYPE_CODE_X86_AMX 記錄

[X86_AMX]

X86_AMX 記錄(代碼 24)會將一個 x86_amx 類型添加到類型表中。

TYPE_CODE_TARGET_TYPE 記錄

[TARGET_TYPE, num_tys, ...ty_params..., ...int_params... ]

TARGET_TYPE 記錄(代碼 26)會將一個目標擴展類型添加到類型表中,其名稱由先前遇到的 STRUCT_NAME 記錄定義。操作數欄位為

  • num_tys:類型參數的數量(與整數參數相對)

  • ty_params:表示類型參數的類型索引

  • int_params:對應於整數參數的數字。

CONSTANTS_BLOCK 內容

CONSTANTS_BLOCK 區塊(id 11)…

FUNCTION_BLOCK 內容

FUNCTION_BLOCK 區塊(id 12)…

除了以下描述的記錄類型之外,FUNCTION_BLOCK 區塊還可能包含以下子區塊

VALUE_SYMTAB_BLOCK 內容

VALUE_SYMTAB_BLOCK 區塊(id 14)…

METADATA_BLOCK 內容

METADATA_BLOCK 區塊(id 15)…

METADATA_ATTACHMENT 內容

METADATA_ATTACHMENT 區塊(id 16)…

STRTAB_BLOCK 內容

STRTAB 區塊(id 23)包含一個單一記錄(STRTAB_BLOB,id 1),其中包含一個單一 Blob 操作數,該操作數包含位元碼檔案的字串表。

字串表中的字串沒有以 null 結尾。記錄的 strtab offsetstrtab size 操作數指定字串在字串表中的位元組偏移量和大小。

字串表會由位元碼檔案中所有在它之前的區塊使用,這些區塊之後沒有另一個 STRTAB 區塊。通常一個位元碼檔案只會有一個字串表,但如果它是透過多個位元碼檔案的二進制串接建立的,則可能會有多個字串表。