除了對今年架構(gòu)的標(biāo)準(zhǔn)支持外,我們還完成了對可擴(kuò)展矩陣擴(kuò)展(SME和SME2)的匯編級支持。在CPU方面,此版本擴(kuò)展了Armv9-A內(nèi)核系列,支持我們的Cortex-A715和Cortex-X3 CPU。
A-profile 2022更新:Armv8.9-A和Armv9.4-A
現(xiàn)在,除了將在下一個LLVM版本中支持的保護(hù)調(diào)用堆棧(GCS)之外,所有擴(kuò)展都可以進(jìn)行匯編和反匯編。Arm C語言擴(kuò)展(ACLE)也用兩個新的內(nèi)部函數(shù)__rsr128和__wsr128進(jìn)行了擴(kuò)展;這些使得新的128位系統(tǒng)寄存器更容易訪問。LLVM現(xiàn)在支持這些內(nèi)部函數(shù)。
轉(zhuǎn)換加固擴(kuò)展(THE)是Armv9.4-A的主要安全改進(jìn)之一,也是虛擬內(nèi)存系統(tǒng)體系結(jié)構(gòu)(VMSA)的一部分。其目的是防止在攻擊者獲得內(nèi)核權(quán)限的情況下對虛擬內(nèi)存的轉(zhuǎn)換表進(jìn)行任意更改。新的讀取-檢查-寫入(RCW)指令已添加到體系結(jié)構(gòu)中,以允許在禁用普通寫入的同時對此類表進(jìn)行受控修改。
盡管這些指令是針對內(nèi)核而非用戶空間開發(fā)人員的,但RCW指令可以很好地映射到C++中128位數(shù)據(jù)類型上的各種原子操作。更具體地說,fetch_and、fetch_or和exchange可以直接用這些指令來實現(xiàn)。
這個功能對任何使用原子操作的人都很有用,所以我們在LLVM 16中添加了代碼生成支持。在LRCPC3和LSE2擴(kuò)展也可用的目標(biāo)中,這些專用指令直接從C++代碼生成,而不需要匯編或內(nèi)部函數(shù)。
以下是std::atomic::fetch_and的示例:
#includestd::atomic<__uint128_t> global; void sink(__uint128_t); void ldclrpal_example(__uint128_t x) { __uint128_t res = global.fetch_and(x); sink(res); } void ldclrp_example(__uint128_t x) { __uint128_t res = global.fetch_and(x, std::memory_order_relaxed); sink(res); }
使用-march=armv9.4a+lse128+rcpc3-O3編譯,生成的程序集顯示正在生成的新指令:
ldclrpal_example(unsigned __int128): mvn x1, x1 mvn x0, x0 adrp x8, global add x8, x8, global ldclrpal x0, x1, [x8] b sink(unsigned __int128) ldclrp_example(unsigned __int128): mvn x1, x1 mvn x0, x0 adrp x8, global add x8, x8, global ldclrp x0, x1, [x8] b sink(unsigned __int128)
多版本控制功能
如今,許多平臺都有一個單一的二進(jìn)制部署模型:每個應(yīng)用程序都是通過一個二進(jìn)制文件分發(fā)的。這使得開發(fā)人員很難針對多個體系結(jié)構(gòu)功能。為了解決這個問題,LLVM 16提供了一種針對特定體系結(jié)構(gòu)特征的方便方式,而不需要處理特征檢測和其他細(xì)節(jié)。這個新功能被稱為函數(shù)多版本控制。
提供了一個新的宏__HAVE_FUNCTION_MULTI_VERSIONING來檢測功能的可用性。如果存在,我們可以要求編譯器通過標(biāo)記__attribute__((target_clones())來生成給定函數(shù)的多個版本。函數(shù)的最合適版本將在運行時調(diào)用。
在下面的示例中,一個函數(shù)被標(biāo)記為要為Advanced SIMD(又名NEON)和SVE構(gòu)建。如果SVE在目標(biāo)上可用,則將使用SVE版本。
#ifdef __HAVE_FUNCTION_MULTI_VERSIONING __attribute__((target_clones("sve", "simd"))) #endif float foo(float *a, float *b) { // }
在某些情況下,開發(fā)人員希望為每個功能提供不同的代碼。這也可以通過使用__attribute__((target_version()))來實現(xiàn)。在下面的例子中,我們?yōu)橥粋€函數(shù)提供了兩個版本。同樣,如果SVE可用,將調(diào)用SVE版本。宏__HAVE_FUNCTION_MULTI_VERSIONING允許編寫與具有和不具有函數(shù)多版本控制的編譯器兼容的代碼。
#ifdef __HAVE_FUNCTION_MULTI_VERSIONING __attribute__((target_version("sve"))) static void foo(void) { printf("FMV uses SVE "); } #endif // this attribute is optional // __attribute__((target_version("default"))) static void foo(void) { printf("FMV default "); return; }
此功能依賴于編譯器rt(-rtlib=編譯器rt),并且在默認(rèn)情況下啟用,但可以使用標(biāo)志-mno fmv禁用它。請注意,函數(shù)多版本控制仍處于測試狀態(tài)。ACLE規(guī)范非常歡迎通過打開新問題或創(chuàng)建pull請求來提供反饋。
性能改進(jìn)
復(fù)數(shù)自動矢量化
LLVM 16包括對復(fù)數(shù)上的公共運算的自動矢量化的支持。這些分別利用了Armv8-A和Armv8-M體系結(jié)構(gòu)的高級SIMD(Neon)和MVE指令集中可用的指令。例如,代碼:
#include#define N 512 void fma (_Complex float a[restrict N], _Complex float b[restrict N], _Complex float c[restrict N]) { for (int i=0; i < N; i++) c[i] = a[i] * b[i]; }
輸出以下匯編代碼:
fma: // @fma mov x8, xzr .LBB0_1: // =>This Inner Loop Header: Depth=1 add x9, x0, x8 add x10, x1, x8 movi v2.2d, #0000000000000000 movi v3.2d, #0000000000000000 ldp q1, q0, [x9] add x9, x2, x8 add x8, x8, #32 cmp x8, #1, lsl #12 // =4096 ldp q5, q4, [x10] fcmla v3.4s, v1.4s, v5.4s, #0 fcmla v2.4s, v0.4s, v4.4s, #0 fcmla v3.4s, v1.4s, v5.4s, #90 fcmla v2.4s, v0.4s, v4.4s, #90 stp q3, q2, [x9] b.ne .LBB0_1 ret
請注意FCMLA指令的使用,該指令對復(fù)數(shù)向量執(zhí)行融合乘加向量運算和可選的復(fù)數(shù)旋轉(zhuǎn)。
默認(rèn)啟用功能專業(yè)化和SPEC2017內(nèi)部改進(jìn)
在為速度進(jìn)行優(yōu)化時,默認(rèn)情況下在所有優(yōu)化級別都啟用了功能的專業(yè)化。通行證的優(yōu)化啟發(fā)式和編譯時屬性已經(jīng)得到了改進(jìn),并且被認(rèn)為通常足夠有益,可以默認(rèn)啟用。
這種優(yōu)化在各種AArch64平臺上特別將SPEC2017 intrate中的505.mcf_r基準(zhǔn)提高了約10%。這有助于將SPEC2017年intrate C/C++基準(zhǔn)在AArch64提高3%。
請注意,SPEC2017性能提升還得益于SelectOpt通道和其他高級模式識別的默認(rèn)調(diào)整和啟用。
SVE和自動矢量化的改進(jìn)
SVE的自動矢量化一直是一個非?;钴S的發(fā)展領(lǐng)域。例如,到目前為止,在條件的不同分支中訪問的指針的矢量化是非常基本的:大多數(shù)時候,它會被計算為成本太高?,F(xiàn)在,指針上的基本運算包含在矢量器的成本模型中。這意味著現(xiàn)在可以在更好的情況下對以下代碼進(jìn)行矢量化:
void foo(float *dst, float *src, int *cond, long disp) { for (long i=0; i<1024; i++) { if (cond[i] != 0) { dst[i] = src[i]; } else { dst[i] = src[i+disp]; } } }
也就是說,在合成示例中,找到合適的環(huán)境以使矢量化有利可圖是很棘手的,并且生成的代碼非常長。如果你想看看矢量化的代碼是什么樣子的,你可以調(diào)整成本模型。使用-march=v9a-O3-Rpass=loop vectorize-mllvm-force target instruction cost=1編譯前面的示例。
通過減少對顯式合并操作的需求,尾部折疊循環(huán)的矢量化也得到了改進(jìn)。例如,以下代碼:
float foo(float *a, float *b) { float sum = 0.0; for (int i = 0; i < 1024; ++i) sum += a[i] * b[i]; return sum; }
用-march=armv9-a-Ofast-mllvm-sve tail folding=all編譯,這表明現(xiàn)在發(fā)出了預(yù)測的FMLA:
.LLVM_15_LOOP: ld1w { z2.s }, p1/z, [x0, x8, lsl #2] ld1w { z3.s }, p1/z, [x1, x8, lsl #2] add x8, x8, x10 fmul z2.s, z3.s, z2.s sel z2.s, p1, z2.s, z0.s whilelo p1.s, x8, x9 fadd z1.s, z1.s, z2.s b.mi .LLVM_15_LOOP .LLVM_16_LOOP: ld1w { z1.s }, p1/z, [x0, x8, lsl #2] ld1w { z2.s }, p1/z, [x1, x8, lsl #2] add x8, x8, x10 fmla z0.s, p1/m, z2.s, z1.s whilelo p1.s, x8, x9 b.mi .LLVM_16_LOOP
此外,通過減少對顯式反向運算的需要,改進(jìn)了具有反向迭代計數(shù)的循環(huán)的矢量化。以這個循環(huán)為例:
void foo(int *a, int *b, int* c) { for (int i = 1024; i >= 0; --i) { if (c[i] > 10) a[i] = b[i] + 5; } }
使用-march=armv9-a-O3編譯后,LLVM 16輸出不再反轉(zhuǎn)加載的數(shù)據(jù),也不再反轉(zhuǎn)用于條件的謂詞:
.LLVM_15_LOOP: ld1w { z0.s }, p0/z, [x16, x9, lsl #2] ld1w { z1.s }, p0/z, [x17, x9, lsl #2] rev z0.s, z0.s rev z1.s, z1.s cmpgt p1.s, p0/z, z0.s, #10 cmpgt p2.s, p0/z, z1.s, #10 rev p1.s, p1.s rev p2.s, p2.s ld1w { z0.s }, p1/z, [x14, x9, lsl #2] ld1w { z1.s }, p2/z, [x15, x9, lsl #2] add z0.s, z0.s, #5 // =0x5 add z1.s, z1.s, #5 // =0x5 st1w { z0.s }, p1, [x12, x9, lsl #2] st1w { z1.s }, p2, [x13, x9, lsl #2] sub x9, x9, x10 cmp x18, x9 b.ne .LLVM_15_LOOP .LLVM_16_LOOP: ld1w { z0.s }, p0/z, [x13, x9, lsl #2] ld1w { z1.s }, p0/z, [x14, x9, lsl #2] cmpgt p1.s, p0/z, z0.s, #10 cmpgt p2.s, p0/z, z1.s, #10 ld1w { z0.s }, p1/z, [x15, x9, lsl #2] ld1w { z1.s }, p2/z, [x16, x9, lsl #2] add z0.s, z0.s, #5 // =0x5 add z1.s, z1.s, #5 // =0x5 st1w { z0.s }, p1, [x17, x9, lsl #2] st1w { z1.s }, p2, [x18, x9, lsl #2] sub x9, x9, x10 cmp x12, x9 b.ne .LLVM_16_LOOP
LLVM 16上SVE的其他性能改進(jìn)包括:
。DUP的使用在各種場景中都得到了極大的改進(jìn),尤其是對于128位LD1RQ變體。
。乘法-加法和乘法子指令可以更廣泛地使用。
。對PTEST指令的需求已經(jīng)大大減少。
。擴(kuò)展循環(huán)負(fù)載消除現(xiàn)在是類型不可知的,因此可以檢測更多的情況。
。SLP成本模型得到了改進(jìn)。
Spec2017與Flang一起構(gòu)建
去年12月,我們通過LLVM/Frang在O3上實現(xiàn)了所有Fortran速率基準(zhǔn)測試的里程碑。主要關(guān)注點是啟用四個失敗的基準(zhǔn)測試(521.wrf_r、527.cam4_r、549.fotonik3d_r、554.roms_r)。主要改進(jìn)之一是通過使用復(fù)雜方言消除了對外部復(fù)雜數(shù)學(xué)庫的依賴。
此外,通過改進(jìn)前端和LLVM之間的信息共享,以及改進(jìn)對快速數(shù)學(xué)的支持,還獲得了一些性能。
您可以通過將-DLLVM_ENABLE_PROJECTS=“Flang;clang;mlir”傳遞給CMake來構(gòu)建Flang。flang可執(zhí)行文件稱為flang-new;確保通過選項-flang實驗exec來生成可執(zhí)行文件。
Target-gated ACLE 內(nèi)聯(lián)
最初是由Highway庫引發(fā)的,目標(biāo)(“
現(xiàn)在支持的格式是:
。arch=
。cpu=
。tune=
。+<feature>,+no<feature>啟用或禁用特定功能,以與GCC目標(biāo)屬性兼容。
。<feature>,no-<feature>啟用或禁用特定功能,以便與以前的clang版本向后兼容。
隨著上述變化,ACLE內(nèi)部函數(shù)的實現(xiàn)也進(jìn)行了修改,使其不再基于預(yù)處理器宏。相反,它們是基于當(dāng)前目標(biāo)啟用的。這允許在單個函數(shù)中提供內(nèi)部函數(shù),而不需要為同一目標(biāo)編譯整個文件。以下示例說明了函數(shù)sve2_log上屬性的使用:
#include#include void base_log(float *src, int *dst, int n) { for(int i = 0; i < n; i++) dst[i] = log2f(src[i]); } void __attribute__((target("sve2"))) sve2_log(float *src, int *dst, int n) { int i = 0; svbool_t p = svwhilelt_b32(i, n); while(svptest_any(svptrue_b32(), p)) { svfloat32_t d = svld1_f32(p, src+i); svint32_t l = svlogb_f32_z(p, d); svst1_s32(p, dst+i, l); i += svcntb(); p = svwhilelt_b32(i, n); } }
llvm objdump的改進(jìn)
在LLVM 16中,Arm目標(biāo)的LLVM objdump的輸出在可讀性和正確性方面得到了改進(jìn),使其成為基于LLVM的工具鏈上GNU objdump的更合適的替代品。
big-endian對象文件的反匯編現(xiàn)在可以正常工作。以前,每個指令字都被意外地進(jìn)行了字節(jié)交換,并被分解為完全不同的東西。
此外,在反匯編中遇到的無法識別的指令會以更有用的方式進(jìn)行處理。以前,反匯編程序只前進(jìn)一個字節(jié),然后從奇數(shù)地址重試。此策略在具有可變長度指令的體系結(jié)構(gòu)上是有意義的,但在Arm上則不然。新的行為是推進(jìn)整個指令,以便文件的其余部分可能會被正確地反匯編。
LLVM 16包括Arm架構(gòu)的其他質(zhì)量改進(jìn),包括Thumb與Arm反匯編的錯誤修復(fù),以及現(xiàn)在包含正確字節(jié)的.byte指令。對指令編碼進(jìn)行了一些可讀性改進(jìn),使Arm和32位Thumb更容易區(qū)分:現(xiàn)在您可以看到Arm指令有一個8位數(shù)字,Thumb有兩個4位數(shù)字,中間有一個空格。
支持AArch64上的嚴(yán)格浮點
AArch64已經(jīng)實現(xiàn)了嚴(yán)格的浮點語義。clang命令行選項-ffp model=strict現(xiàn)在在AArch64目標(biāo)上被接受,而不是被忽略并發(fā)出警告。舉個例子,只有在安全的情況下才執(zhí)行FP除法:
float fn(int n, float x, float y) { if (n == 0) { x += 1; } else { x += y/n; } return x; }
在LLVM 15上,使用-O2進(jìn)行編譯會生成以下代碼:
fn(int, float, float): // @fn(int, float, float) scvtf s3, w0 fmov s2, #1.00000000 cmp w0, #0 fdiv s1, s1, s3 fadd s1, s1, s0 fadd s0, s0, s2 fcsel s0, s1, s0, ne ret
它將執(zhí)行兩個分支,包括除法,然后在fcsel中選擇正確的結(jié)果。盡管保留了代碼的功能,但當(dāng)n=0時,它會導(dǎo)致偽FE_DIVBYZERO浮點異常。在LLVM 16上,使用-O2-ffp模型=嚴(yán)格編譯會產(chǎn)生以下代碼:
fn(int, float, float): // @fn(int, float, float) cbz w0, .LBB0_2 scvtf s2, w0 fdiv s1, s1, s2 fadd s0, s0, s1 ret .LBB0_2: mov w8, #1 scvtf s1, w8 fadd s0, s0, s1 ret
其中兩個不同的執(zhí)行分支保持分離,從而防止FP異常的發(fā)生。
由于支持嚴(yán)格的FP,現(xiàn)在也接受了選項-frapping math和-frounding math。一方面,-ftrapping數(shù)學(xué)確保代碼不會引入或刪除任何類型的FP異常可能導(dǎo)致的副作用。其中包括軟件可以通過檢查FPSR異步檢測到的異常。類似地,-founding數(shù)學(xué)避免應(yīng)用假設(shè)特定FP舍入行為的優(yōu)化。
在編譯器rt和LLD中支持早期的Arm體系結(jié)構(gòu)
LLD現(xiàn)在可以用作ARMv4和ARMv4T的鏈接器:它現(xiàn)在發(fā)出與ARMv4和ARMv4T兼容的thunk,而不是ARMv4的不兼容BX指令或ARMv4或ARMv4T的BLX指令。
與此相關(guān)的是,為ARMv4T、ARMv5TE和ARMv6添加了對編譯器rt內(nèi)置程序的支持,從而解鎖了對這些體系結(jié)構(gòu)的運行時支持。
由于這項啟用工作,現(xiàn)在可以為這些32位Arm架構(gòu)提供一個完整的基于LLVM的工具鏈。因此,Linux內(nèi)核現(xiàn)在增加了對使用LLD構(gòu)建Clang的支持,Rust程序不再需要依賴GNU鏈接器。
審核編輯:劉清
-
寄存器
+關(guān)注
關(guān)注
31文章
5336瀏覽量
120230 -
ARM處理器
+關(guān)注
關(guān)注
6文章
360瀏覽量
41720 -
編譯器
+關(guān)注
關(guān)注
1文章
1623瀏覽量
49108 -
SIMD
+關(guān)注
關(guān)注
0文章
33瀏覽量
10289 -
GNU
+關(guān)注
關(guān)注
0文章
143瀏覽量
17492
原文標(biāo)題:LLVM16的新增功能
文章出處:【微信號:Arm軟件開發(fā)者,微信公眾號:Arm軟件開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論