[llvm] [TLI] Add x86 libmvec mappings for GLIBC 2.35 vector functions (PR #206274)

Tõnu Samuel via llvm-commits llvm-commits at lists.llvm.org
Sat Jun 27 11:26:53 PDT 2026


https://github.com/tonuonu created https://github.com/llvm/llvm-project/pull/206274

## Summary

glibc 2.35 extended x86_64 libmvec with vector implementations of `erf`, `erfc`,
`cbrt`, `expm1`, `log1p`, `asinh`, `acosh` and `atanh` (among others), but LLVM's
`LIBMVEC_X86` table in `VecFuncs.def` was never updated past the original glibc-2.22
set. As a result `clang -fveclib=libmvec` cannot vectorize loops over these functions
on x86_64, even though the vector symbols are present in the linked `libmvec.so`. The
AArch64 libmvec table already maps several of them.

This patch adds the x86 mappings for the 8 GLIBC-2.35 functions that have **no
corresponding LLVM intrinsic** (pure named/TLI mappings). The intrinsic-backed
additions (`sinh`, `cosh`, `tanh`, `asin`, `acos`, `atan`, `exp2`, `exp10`, `log2`,
`log10`) are left for a follow-up. The mappings mirror the existing `exp`/`log`
entries — only the SSE (`_ZGVbN…`) and AVX2 (`_ZGVdN…`) widths; the **AVX-512
(`_ZGVeN…`) variants are intentionally omitted** until the 512-bit-call-on-narrower-
target miscompilation (#204930) is resolved. The symbols are verified present in
glibc ≥ 2.35.

Tracks #206273.

## Test

Extends `llvm/test/Transforms/LoopVectorize/X86/libm-vector-calls.ll` with a
vectorization check per added function. `add-TLI-mappings.ll` is unaffected
(verified locally for both the x86 and AArch64 run lines).

## Motivation

These functions are currently unreachable via `-fveclib=libmvec` on x86, so loops
over them stay scalar. best-of-20 timing of a 16M-element scalar libm loop vs the
libmvec-vectorized loop (AVX2 / `ZGVdN` path — i.e. exactly what this patch enables):

| function | Zen (AVX2) | Xeon W-2235 (AVX2) |
|---|---|---|
| erf   | 13.4× | 15.7× |
| erfc  |  8.8× | 12.4× |
| cbrt  |  6.8× |  9.1× |
| expm1 |  7.5× | 12.0× |
| log1p |  6.7× | 10.8× |
| asinh |  1.9× |  3.9× |
| acosh |  1.6× |  4.1× |
| atanh |  4.2× |  8.5× |

gcc already emits these via libmvec; this brings clang `-fveclib=libmvec` to parity.


>From 036bbdcb6e49566357d7ff9d8886f35dbc3bf506 Mon Sep 17 00:00:00 2001
From: Tonu Samuel <tonu at spam.ee>
Date: Sat, 27 Jun 2026 21:19:47 +0300
Subject: [PATCH] [TLI] Add x86 libmvec mappings for GLIBC 2.35 vector
 functions

glibc 2.35 added x86_64 libmvec vector implementations of erf, erfc, cbrt,
expm1, log1p, asinh, acosh and atanh, but LLVM's x86 libmvec table in
VecFuncs.def was never updated, so -fveclib=libmvec could not vectorize
loops over these functions on x86 (the AArch64 libmvec table already maps
them). Add the missing SSE/AVX2 mappings, matching the existing exp/log
entries, and extend the LoopVectorize test.
---
 llvm/include/llvm/Analysis/VecFuncs.def       |  50 ++++
 .../LoopVectorize/X86/libm-vector-calls.ll    | 220 ++++++++++++++++++
 2 files changed, 270 insertions(+)

diff --git a/llvm/include/llvm/Analysis/VecFuncs.def b/llvm/include/llvm/Analysis/VecFuncs.def
index e7b949bc073ae..14327197cf9d3 100644
--- a/llvm/include/llvm/Analysis/VecFuncs.def
+++ b/llvm/include/llvm/Analysis/VecFuncs.def
@@ -237,6 +237,56 @@ TLI_DEFINE_VECFUNC("llvm.log.f64", "_ZGVdN4v_log", FIXED(4), "_ZGV_LLVM_N4v")
 TLI_DEFINE_VECFUNC("llvm.log.f32", "_ZGVbN4v_logf", FIXED(4), "_ZGV_LLVM_N4v")
 TLI_DEFINE_VECFUNC("llvm.log.f32", "_ZGVdN8v_logf", FIXED(8), "_ZGV_LLVM_N8v")
 
+// Functions added to libmvec in GLIBC 2.35 (no corresponding LLVM intrinsic).
+
+TLI_DEFINE_VECFUNC("erf", "_ZGVbN2v_erf", FIXED(2), "_ZGV_LLVM_N2v")
+TLI_DEFINE_VECFUNC("erf", "_ZGVdN4v_erf", FIXED(4), "_ZGV_LLVM_N4v")
+
+TLI_DEFINE_VECFUNC("erff", "_ZGVbN4v_erff", FIXED(4), "_ZGV_LLVM_N4v")
+TLI_DEFINE_VECFUNC("erff", "_ZGVdN8v_erff", FIXED(8), "_ZGV_LLVM_N8v")
+
+TLI_DEFINE_VECFUNC("erfc", "_ZGVbN2v_erfc", FIXED(2), "_ZGV_LLVM_N2v")
+TLI_DEFINE_VECFUNC("erfc", "_ZGVdN4v_erfc", FIXED(4), "_ZGV_LLVM_N4v")
+
+TLI_DEFINE_VECFUNC("erfcf", "_ZGVbN4v_erfcf", FIXED(4), "_ZGV_LLVM_N4v")
+TLI_DEFINE_VECFUNC("erfcf", "_ZGVdN8v_erfcf", FIXED(8), "_ZGV_LLVM_N8v")
+
+TLI_DEFINE_VECFUNC("cbrt", "_ZGVbN2v_cbrt", FIXED(2), "_ZGV_LLVM_N2v")
+TLI_DEFINE_VECFUNC("cbrt", "_ZGVdN4v_cbrt", FIXED(4), "_ZGV_LLVM_N4v")
+
+TLI_DEFINE_VECFUNC("cbrtf", "_ZGVbN4v_cbrtf", FIXED(4), "_ZGV_LLVM_N4v")
+TLI_DEFINE_VECFUNC("cbrtf", "_ZGVdN8v_cbrtf", FIXED(8), "_ZGV_LLVM_N8v")
+
+TLI_DEFINE_VECFUNC("expm1", "_ZGVbN2v_expm1", FIXED(2), "_ZGV_LLVM_N2v")
+TLI_DEFINE_VECFUNC("expm1", "_ZGVdN4v_expm1", FIXED(4), "_ZGV_LLVM_N4v")
+
+TLI_DEFINE_VECFUNC("expm1f", "_ZGVbN4v_expm1f", FIXED(4), "_ZGV_LLVM_N4v")
+TLI_DEFINE_VECFUNC("expm1f", "_ZGVdN8v_expm1f", FIXED(8), "_ZGV_LLVM_N8v")
+
+TLI_DEFINE_VECFUNC("log1p", "_ZGVbN2v_log1p", FIXED(2), "_ZGV_LLVM_N2v")
+TLI_DEFINE_VECFUNC("log1p", "_ZGVdN4v_log1p", FIXED(4), "_ZGV_LLVM_N4v")
+
+TLI_DEFINE_VECFUNC("log1pf", "_ZGVbN4v_log1pf", FIXED(4), "_ZGV_LLVM_N4v")
+TLI_DEFINE_VECFUNC("log1pf", "_ZGVdN8v_log1pf", FIXED(8), "_ZGV_LLVM_N8v")
+
+TLI_DEFINE_VECFUNC("asinh", "_ZGVbN2v_asinh", FIXED(2), "_ZGV_LLVM_N2v")
+TLI_DEFINE_VECFUNC("asinh", "_ZGVdN4v_asinh", FIXED(4), "_ZGV_LLVM_N4v")
+
+TLI_DEFINE_VECFUNC("asinhf", "_ZGVbN4v_asinhf", FIXED(4), "_ZGV_LLVM_N4v")
+TLI_DEFINE_VECFUNC("asinhf", "_ZGVdN8v_asinhf", FIXED(8), "_ZGV_LLVM_N8v")
+
+TLI_DEFINE_VECFUNC("acosh", "_ZGVbN2v_acosh", FIXED(2), "_ZGV_LLVM_N2v")
+TLI_DEFINE_VECFUNC("acosh", "_ZGVdN4v_acosh", FIXED(4), "_ZGV_LLVM_N4v")
+
+TLI_DEFINE_VECFUNC("acoshf", "_ZGVbN4v_acoshf", FIXED(4), "_ZGV_LLVM_N4v")
+TLI_DEFINE_VECFUNC("acoshf", "_ZGVdN8v_acoshf", FIXED(8), "_ZGV_LLVM_N8v")
+
+TLI_DEFINE_VECFUNC("atanh", "_ZGVbN2v_atanh", FIXED(2), "_ZGV_LLVM_N2v")
+TLI_DEFINE_VECFUNC("atanh", "_ZGVdN4v_atanh", FIXED(4), "_ZGV_LLVM_N4v")
+
+TLI_DEFINE_VECFUNC("atanhf", "_ZGVbN4v_atanhf", FIXED(4), "_ZGV_LLVM_N4v")
+TLI_DEFINE_VECFUNC("atanhf", "_ZGVdN8v_atanhf", FIXED(8), "_ZGV_LLVM_N8v")
+
 #elif defined(TLI_DEFINE_LIBMVEC_AARCH64_VECFUNCS)
 
 TLI_DEFINE_VECFUNC("acos", "_ZGVnN2v_acos", FIXED(2), NOMASK, "_ZGV_LLVM_N2v", CallingConv::AArch64_VectorCall)
diff --git a/llvm/test/Transforms/LoopVectorize/X86/libm-vector-calls.ll b/llvm/test/Transforms/LoopVectorize/X86/libm-vector-calls.ll
index 071f262ec903b..7afb7d1a787be 100644
--- a/llvm/test/Transforms/LoopVectorize/X86/libm-vector-calls.ll
+++ b/llvm/test/Transforms/LoopVectorize/X86/libm-vector-calls.ll
@@ -365,3 +365,223 @@ declare float @cosf(float) #0
 declare float @expf(float) #0
 declare float @powf(float, float) #0
 declare float @logf(float) #0
+
+
+; GLIBC 2.35 libmvec functions (no corresponding LLVM intrinsic)
+declare float @erff(float) #0
+declare float @erfcf(float) #0
+declare float @cbrtf(float) #0
+declare float @expm1f(float) #0
+declare float @log1pf(float) #0
+declare float @asinhf(float) #0
+declare float @acoshf(float) #0
+declare float @atanhf(float) #0
+
+define void @erf_f32(ptr nocapture %varray) {
+; CHECK-LABEL: @erf_f32
+; CHECK-LABEL:    vector.body
+; CHECK: <4 x float> @_ZGVbN4v_erff
+entry:
+  br label %for.body
+
+for.body:
+  %indvars.iv = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]
+  %tmp = trunc i64 %indvars.iv to i32
+  %conv = sitofp i32 %tmp to float
+  %call = tail call fast float @erff(float %conv)
+  %arrayidx = getelementptr inbounds float, ptr %varray, i64 %indvars.iv
+  store float %call, ptr %arrayidx, align 4
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %exitcond = icmp eq i64 %indvars.iv.next, 1000
+  br i1 %exitcond, label %for.end, label %for.body, !llvm.loop !134
+
+for.end:
+  ret void
+}
+
+!134 = distinct !{!134, !135, !136}
+!135 = !{!"llvm.loop.vectorize.width", i32 4}
+!136 = !{!"llvm.loop.vectorize.enable", i1 true}
+
+define void @erfc_f32(ptr nocapture %varray) {
+; CHECK-LABEL: @erfc_f32
+; CHECK-LABEL:    vector.body
+; CHECK: <4 x float> @_ZGVbN4v_erfcf
+entry:
+  br label %for.body
+
+for.body:
+  %indvars.iv = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]
+  %tmp = trunc i64 %indvars.iv to i32
+  %conv = sitofp i32 %tmp to float
+  %call = tail call fast float @erfcf(float %conv)
+  %arrayidx = getelementptr inbounds float, ptr %varray, i64 %indvars.iv
+  store float %call, ptr %arrayidx, align 4
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %exitcond = icmp eq i64 %indvars.iv.next, 1000
+  br i1 %exitcond, label %for.end, label %for.body, !llvm.loop !137
+
+for.end:
+  ret void
+}
+
+!137 = distinct !{!137, !138, !139}
+!138 = !{!"llvm.loop.vectorize.width", i32 4}
+!139 = !{!"llvm.loop.vectorize.enable", i1 true}
+
+define void @cbrt_f32(ptr nocapture %varray) {
+; CHECK-LABEL: @cbrt_f32
+; CHECK-LABEL:    vector.body
+; CHECK: <4 x float> @_ZGVbN4v_cbrtf
+entry:
+  br label %for.body
+
+for.body:
+  %indvars.iv = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]
+  %tmp = trunc i64 %indvars.iv to i32
+  %conv = sitofp i32 %tmp to float
+  %call = tail call fast float @cbrtf(float %conv)
+  %arrayidx = getelementptr inbounds float, ptr %varray, i64 %indvars.iv
+  store float %call, ptr %arrayidx, align 4
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %exitcond = icmp eq i64 %indvars.iv.next, 1000
+  br i1 %exitcond, label %for.end, label %for.body, !llvm.loop !140
+
+for.end:
+  ret void
+}
+
+!140 = distinct !{!140, !141, !142}
+!141 = !{!"llvm.loop.vectorize.width", i32 4}
+!142 = !{!"llvm.loop.vectorize.enable", i1 true}
+
+define void @expm1_f32(ptr nocapture %varray) {
+; CHECK-LABEL: @expm1_f32
+; CHECK-LABEL:    vector.body
+; CHECK: <4 x float> @_ZGVbN4v_expm1f
+entry:
+  br label %for.body
+
+for.body:
+  %indvars.iv = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]
+  %tmp = trunc i64 %indvars.iv to i32
+  %conv = sitofp i32 %tmp to float
+  %call = tail call fast float @expm1f(float %conv)
+  %arrayidx = getelementptr inbounds float, ptr %varray, i64 %indvars.iv
+  store float %call, ptr %arrayidx, align 4
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %exitcond = icmp eq i64 %indvars.iv.next, 1000
+  br i1 %exitcond, label %for.end, label %for.body, !llvm.loop !143
+
+for.end:
+  ret void
+}
+
+!143 = distinct !{!143, !144, !145}
+!144 = !{!"llvm.loop.vectorize.width", i32 4}
+!145 = !{!"llvm.loop.vectorize.enable", i1 true}
+
+define void @log1p_f32(ptr nocapture %varray) {
+; CHECK-LABEL: @log1p_f32
+; CHECK-LABEL:    vector.body
+; CHECK: <4 x float> @_ZGVbN4v_log1pf
+entry:
+  br label %for.body
+
+for.body:
+  %indvars.iv = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]
+  %tmp = trunc i64 %indvars.iv to i32
+  %conv = sitofp i32 %tmp to float
+  %call = tail call fast float @log1pf(float %conv)
+  %arrayidx = getelementptr inbounds float, ptr %varray, i64 %indvars.iv
+  store float %call, ptr %arrayidx, align 4
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %exitcond = icmp eq i64 %indvars.iv.next, 1000
+  br i1 %exitcond, label %for.end, label %for.body, !llvm.loop !146
+
+for.end:
+  ret void
+}
+
+!146 = distinct !{!146, !147, !148}
+!147 = !{!"llvm.loop.vectorize.width", i32 4}
+!148 = !{!"llvm.loop.vectorize.enable", i1 true}
+
+define void @asinh_f32(ptr nocapture %varray) {
+; CHECK-LABEL: @asinh_f32
+; CHECK-LABEL:    vector.body
+; CHECK: <4 x float> @_ZGVbN4v_asinhf
+entry:
+  br label %for.body
+
+for.body:
+  %indvars.iv = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]
+  %tmp = trunc i64 %indvars.iv to i32
+  %conv = sitofp i32 %tmp to float
+  %call = tail call fast float @asinhf(float %conv)
+  %arrayidx = getelementptr inbounds float, ptr %varray, i64 %indvars.iv
+  store float %call, ptr %arrayidx, align 4
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %exitcond = icmp eq i64 %indvars.iv.next, 1000
+  br i1 %exitcond, label %for.end, label %for.body, !llvm.loop !149
+
+for.end:
+  ret void
+}
+
+!149 = distinct !{!149, !150, !151}
+!150 = !{!"llvm.loop.vectorize.width", i32 4}
+!151 = !{!"llvm.loop.vectorize.enable", i1 true}
+
+define void @acosh_f32(ptr nocapture %varray) {
+; CHECK-LABEL: @acosh_f32
+; CHECK-LABEL:    vector.body
+; CHECK: <4 x float> @_ZGVbN4v_acoshf
+entry:
+  br label %for.body
+
+for.body:
+  %indvars.iv = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]
+  %tmp = trunc i64 %indvars.iv to i32
+  %conv = sitofp i32 %tmp to float
+  %call = tail call fast float @acoshf(float %conv)
+  %arrayidx = getelementptr inbounds float, ptr %varray, i64 %indvars.iv
+  store float %call, ptr %arrayidx, align 4
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %exitcond = icmp eq i64 %indvars.iv.next, 1000
+  br i1 %exitcond, label %for.end, label %for.body, !llvm.loop !152
+
+for.end:
+  ret void
+}
+
+!152 = distinct !{!152, !153, !154}
+!153 = !{!"llvm.loop.vectorize.width", i32 4}
+!154 = !{!"llvm.loop.vectorize.enable", i1 true}
+
+define void @atanh_f32(ptr nocapture %varray) {
+; CHECK-LABEL: @atanh_f32
+; CHECK-LABEL:    vector.body
+; CHECK: <4 x float> @_ZGVbN4v_atanhf
+entry:
+  br label %for.body
+
+for.body:
+  %indvars.iv = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]
+  %tmp = trunc i64 %indvars.iv to i32
+  %conv = sitofp i32 %tmp to float
+  %call = tail call fast float @atanhf(float %conv)
+  %arrayidx = getelementptr inbounds float, ptr %varray, i64 %indvars.iv
+  store float %call, ptr %arrayidx, align 4
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %exitcond = icmp eq i64 %indvars.iv.next, 1000
+  br i1 %exitcond, label %for.end, label %for.body, !llvm.loop !155
+
+for.end:
+  ret void
+}
+
+!155 = distinct !{!155, !156, !157}
+!156 = !{!"llvm.loop.vectorize.width", i32 4}
+!157 = !{!"llvm.loop.vectorize.enable", i1 true}
+



More information about the llvm-commits mailing list