[llvm] [SimplifyLibCalls] Combine effectful sin and cos pairs (PR #228793)
Dávid Bolvanský via llvm-commits
llvm-commits at lists.llvm.org
Sat Oct 3 17:38:45 PDT 2026
https://github.com/davidbolvansky created https://github.com/llvm/llvm-project/pull/228793
Combine same-argument `sin`/`cos` (and `sinf`/`cosf`) calls in one basic block into an effectful `sincos` libcall on GNU/Linux. A bounded scan rejects intervening memory accesses or side effects, strict FP calls and incompatible call attributes.
Motivated by GraphicsMagick Swirl, where Clang emits two adjacent libcalls while GCC emits one `sincos`. The combined call retains memory effects rather than converting errno-writing calls to pure intrinsics.
Draft for investigating target-libm equivalence, including errno and floating-point edge cases, and profitability of the output-pointer ABI. No end-to-end speedup is claimed yet.
Developed with assistance from OpenAI Codex.
>From 1513df309f148b217a40f4a9b46e530d7a71ff1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C3=A1vid=20Bolvansk=C3=BD?= <david.bolvansky at gmail.com>
Date: Sun, 4 Oct 2026 02:32:31 +0200
Subject: [PATCH 1/3] [InstCombine] Add tests for effectful sin and cos pairs
---
.../Transforms/InstCombine/sincos-libcalls.ll | 134 ++++++++++++++++++
1 file changed, 134 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/sincos-libcalls.ll
diff --git a/llvm/test/Transforms/InstCombine/sincos-libcalls.ll b/llvm/test/Transforms/InstCombine/sincos-libcalls.ll
new file mode 100644
index 0000000000000..27f087c37bdc7
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/sincos-libcalls.ll
@@ -0,0 +1,134 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+target triple = "x86_64-unknown-linux-gnu"
+
+define double @adjacent(double %x, double %y, ptr %errno) {
+; CHECK-LABEL: @adjacent(
+; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]])
+; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[X]])
+; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %s = call double @sin(double %x)
+ %c = call double @cos(double %x)
+ %r = fadd double %s, %c
+ ret double %r
+}
+
+define float @reverse_float(float %x, float %y, ptr %errno) {
+; CHECK-LABEL: @reverse_float(
+; CHECK-NEXT: [[S:%.*]] = call float @cosf(float [[X:%.*]])
+; CHECK-NEXT: [[C:%.*]] = call float @sinf(float [[X]])
+; CHECK-NEXT: [[R:%.*]] = fadd float [[S]], [[C]]
+; CHECK-NEXT: ret float [[R]]
+;
+ %s = call float @cosf(float %x)
+ %c = call float @sinf(float %x)
+ %r = fadd float %s, %c
+ ret float %r
+}
+
+define double @pure_between(double %x, double %y, ptr %errno) {
+; CHECK-LABEL: @pure_between(
+; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]])
+; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[X]])
+; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %s = call double @sin(double %x)
+ %extra = fadd double %x, 1.0
+ %c = call double @cos(double %x)
+ %r = fadd double %s, %c
+ ret double %r
+}
+
+define double @errno_read(double %x, double %y, ptr %errno) {
+; CHECK-LABEL: @errno_read(
+; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]])
+; CHECK-NEXT: [[E:%.*]] = load volatile i32, ptr [[ERRNO:%.*]], align 4
+; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[X]])
+; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %s = call double @sin(double %x)
+ %e = load volatile i32, ptr %errno
+ %c = call double @cos(double %x)
+ %r = fadd double %s, %c
+ ret double %r
+}
+
+define double @errno_write(double %x, double %y, ptr %errno) {
+; CHECK-LABEL: @errno_write(
+; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]])
+; CHECK-NEXT: store i32 0, ptr [[ERRNO:%.*]], align 4
+; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[X]])
+; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %s = call double @sin(double %x)
+ store i32 0, ptr %errno
+ %c = call double @cos(double %x)
+ %r = fadd double %s, %c
+ ret double %r
+}
+
+define double @intervening_call(double %x, double %y, ptr %errno) {
+; CHECK-LABEL: @intervening_call(
+; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]])
+; CHECK-NEXT: call void @effect()
+; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[X]])
+; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %s = call double @sin(double %x)
+ call void @effect()
+ %c = call double @cos(double %x)
+ %r = fadd double %s, %c
+ ret double %r
+}
+
+define double @strict(double %x, double %y, ptr %errno) strictfp {
+; CHECK-LABEL: @strict(
+; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]])
+; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[X]])
+; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %s = call double @sin(double %x)
+ %c = call double @cos(double %x)
+ %r = fadd double %s, %c
+ ret double %r
+}
+
+define double @different_argument(double %x, double %y, ptr %errno) {
+; CHECK-LABEL: @different_argument(
+; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]])
+; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[Y:%.*]])
+; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %s = call double @sin(double %x)
+ %c = call double @cos(double %y)
+ %r = fadd double %s, %c
+ ret double %r
+}
+
+define double @nobuiltin(double %x, double %y, ptr %errno) {
+; CHECK-LABEL: @nobuiltin(
+; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]])
+; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[X]]) #[[ATTR1:[0-9]+]]
+; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %s = call double @sin(double %x)
+ %c = call double @cos(double %x) nobuiltin
+ %r = fadd double %s, %c
+ ret double %r
+}
+
+declare double @sin(double)
+declare double @cos(double)
+declare float @sinf(float)
+declare float @cosf(float)
+declare void @effect()
>From 8ae7550b465d5c3b1d12d76af7247c527dcb27f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C3=A1vid=20Bolvansk=C3=BD?= <david.bolvansky at gmail.com>
Date: Sun, 4 Oct 2026 02:37:44 +0200
Subject: [PATCH 2/3] [SimplifyLibCalls] Combine effectful sin and cos pairs on
GNU Linux
---
.../llvm/Transforms/Utils/SimplifyLibCalls.h | 1 +
.../lib/Transforms/Utils/SimplifyLibCalls.cpp | 69 ++++++++++++++++++-
.../Transforms/InstCombine/sincos-libcalls.ll | 28 +++++---
3 files changed, 89 insertions(+), 9 deletions(-)
diff --git a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
index 7298ad459a5da..e61a2d970d266 100644
--- a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
+++ b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
@@ -208,6 +208,7 @@ class LibCallSimplifier {
Value *optimizeSqrt(CallInst *CI, IRBuilderBase &B);
Value *optimizeFMod(CallInst *CI, IRBuilderBase &B);
Value *mergeSqrtToExp(CallInst *CI, IRBuilderBase &B);
+ Value *optimizeSinCos(CallInst *CI, LibFunc Func, IRBuilderBase &B);
Value *optimizeSinCosPi(CallInst *CI, bool IsSin, IRBuilderBase &B);
Value *optimizeTrigInversionPairs(CallInst *CI, IRBuilderBase &B);
Value *optimizeSymmetric(CallInst *CI, LibFunc Func, IRBuilderBase &B);
diff --git a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
index 6679a0fc4348b..4fbd11a36f47b 100644
--- a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
@@ -3199,6 +3199,67 @@ Value *LibCallSimplifier::optimizeSymmetric(CallInst *CI, LibFunc Func,
}
}
+Value *LibCallSimplifier::optimizeSinCos(CallInst *CI, LibFunc Func,
+ IRBuilderBase &B) {
+ bool IsSin = Func == LibFunc_sin || Func == LibFunc_sinf;
+ LibFunc OtherFunc = CI->getType()->isFloatTy()
+ ? (IsSin ? LibFunc_cosf : LibFunc_sinf)
+ : (IsSin ? LibFunc_cos : LibFunc_sin);
+ LibFunc CombinedFunc =
+ CI->getType()->isFloatTy() ? LibFunc_sincosf : LibFunc_sincos;
+ Module *M = CI->getModule();
+ const Triple &T = M->getTargetTriple();
+ // Initially restrict effectful pairing to the GNU libm implementation.
+ if (!T.isOSLinux() || !T.isGNUEnvironment() ||
+ !isLibFuncEmittable(M, TLI, CombinedFunc) ||
+ CI->getCallingConv() != CallingConv::C || CI->hasOperandBundles() ||
+ CI->isMustTailCall() || CI->isConvergent() ||
+ DL.getAllocaAddrSpace() != 0)
+ return nullptr;
+
+ CallInst *Other = nullptr;
+ unsigned NumVisited = 0;
+ for (Instruction *I = CI->getNextNode(); I && ++NumVisited <= 16;
+ I = I->getNextNode()) {
+ if (auto *Call = dyn_cast<CallInst>(I)) {
+ Function *Callee = Call->getCalledFunction();
+ if (Callee && TLI->getLibFunc(*Callee) == OtherFunc &&
+ Call->arg_size() == 1 &&
+ Call->getArgOperand(0) == CI->getArgOperand(0) &&
+ Call->getCallingConv() == CallingConv::C &&
+ !Call->hasOperandBundles() && !Call->isMustTailCall() &&
+ !Call->isStrictFP() && !Call->isConvergent() &&
+ !Call->isNoBuiltin()) {
+ Other = Call;
+ break;
+ }
+ }
+ // Reads can observe errno from the first call, and writes can reset it.
+ // Do not move effects across either kind of access or an early exit.
+ if (I->mayReadOrWriteMemory() || I->mayHaveSideEffects() ||
+ I->isTerminator())
+ break;
+ }
+ if (!Other)
+ return nullptr;
+
+ Type *Ty = CI->getType();
+ IRBuilder<> EntryBuilder(
+ &*CI->getFunction()->getEntryBlock().getFirstInsertionPt());
+ Value *SinOut = EntryBuilder.CreateAlloca(Ty, nullptr, "sin.out");
+ Value *CosOut = EntryBuilder.CreateAlloca(Ty, nullptr, "cos.out");
+ FunctionCallee Callee =
+ getOrInsertLibFunc(M, *TLI, CombinedFunc, B.getVoidTy(), Ty,
+ SinOut->getType(), CosOut->getType());
+ // Keep the combined operation as an effectful libcall, not a pure intrinsic.
+ B.CreateCall(Callee, {CI->getArgOperand(0), SinOut, CosOut});
+ Value *Sin = B.CreateLoad(Ty, SinOut, "sin");
+ Value *Cos = B.CreateLoad(Ty, CosOut, "cos");
+ replaceAllUsesWith(Other, IsSin ? Cos : Sin);
+ eraseFromParent(Other);
+ return IsSin ? Sin : Cos;
+}
+
Value *LibCallSimplifier::optimizeSinCosPi(CallInst *CI, bool IsSin, IRBuilderBase &B) {
// Make sure the prototype is as expected, otherwise the rest of the
// function is probably invalid and likely to abort.
@@ -4180,11 +4241,17 @@ Value *LibCallSimplifier::optimizeFloatingPointLibCall(CallInst *CI,
case LibFunc_cospi:
return optimizeSinCosPi(CI, /*IsSin*/false, Builder);
case LibFunc_sinf:
+ if (!CI->doesNotAccessMemory())
+ return optimizeSinCos(CI, Func, Builder);
+ [[fallthrough]];
case LibFunc_sinl:
if (CI->doesNotAccessMemory())
return replaceUnaryCall(CI, Builder, Intrinsic::sin);
return nullptr;
case LibFunc_cosf:
+ if (!CI->doesNotAccessMemory())
+ return optimizeSinCos(CI, Func, Builder);
+ [[fallthrough]];
case LibFunc_cosl:
if (CI->doesNotAccessMemory())
return replaceUnaryCall(CI, Builder, Intrinsic::cos);
@@ -4276,7 +4343,7 @@ Value *LibCallSimplifier::optimizeFloatingPointLibCall(CallInst *CI,
if (CI->doesNotAccessMemory())
return replaceUnaryCall(
CI, Builder, Func == LibFunc_sin ? Intrinsic::sin : Intrinsic::cos);
- return nullptr;
+ return optimizeSinCos(CI, Func, Builder);
case LibFunc_acos:
case LibFunc_acosh:
case LibFunc_asin:
diff --git a/llvm/test/Transforms/InstCombine/sincos-libcalls.ll b/llvm/test/Transforms/InstCombine/sincos-libcalls.ll
index 27f087c37bdc7..59f329701b31c 100644
--- a/llvm/test/Transforms/InstCombine/sincos-libcalls.ll
+++ b/llvm/test/Transforms/InstCombine/sincos-libcalls.ll
@@ -5,8 +5,11 @@ target triple = "x86_64-unknown-linux-gnu"
define double @adjacent(double %x, double %y, ptr %errno) {
; CHECK-LABEL: @adjacent(
-; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]])
-; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[X]])
+; CHECK-NEXT: [[SIN_OUT:%.*]] = alloca double, align 8
+; CHECK-NEXT: [[COS_OUT:%.*]] = alloca double, align 8
+; CHECK-NEXT: call void @sincos(double [[X:%.*]], ptr nonnull [[SIN_OUT]], ptr nonnull [[COS_OUT]])
+; CHECK-NEXT: [[S:%.*]] = load double, ptr [[SIN_OUT]], align 8
+; CHECK-NEXT: [[C:%.*]] = load double, ptr [[COS_OUT]], align 8
; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
; CHECK-NEXT: ret double [[R]]
;
@@ -18,8 +21,11 @@ define double @adjacent(double %x, double %y, ptr %errno) {
define float @reverse_float(float %x, float %y, ptr %errno) {
; CHECK-LABEL: @reverse_float(
-; CHECK-NEXT: [[S:%.*]] = call float @cosf(float [[X:%.*]])
-; CHECK-NEXT: [[C:%.*]] = call float @sinf(float [[X]])
+; CHECK-NEXT: [[SIN_OUT:%.*]] = alloca float, align 4
+; CHECK-NEXT: [[COS_OUT:%.*]] = alloca float, align 4
+; CHECK-NEXT: call void @sincosf(float [[X:%.*]], ptr nonnull [[SIN_OUT]], ptr nonnull [[COS_OUT]])
+; CHECK-NEXT: [[C:%.*]] = load float, ptr [[SIN_OUT]], align 4
+; CHECK-NEXT: [[S:%.*]] = load float, ptr [[COS_OUT]], align 4
; CHECK-NEXT: [[R:%.*]] = fadd float [[S]], [[C]]
; CHECK-NEXT: ret float [[R]]
;
@@ -31,8 +37,11 @@ define float @reverse_float(float %x, float %y, ptr %errno) {
define double @pure_between(double %x, double %y, ptr %errno) {
; CHECK-LABEL: @pure_between(
-; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]])
-; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[X]])
+; CHECK-NEXT: [[SIN_OUT:%.*]] = alloca double, align 8
+; CHECK-NEXT: [[COS_OUT:%.*]] = alloca double, align 8
+; CHECK-NEXT: call void @sincos(double [[X:%.*]], ptr nonnull [[SIN_OUT]], ptr nonnull [[COS_OUT]])
+; CHECK-NEXT: [[S:%.*]] = load double, ptr [[SIN_OUT]], align 8
+; CHECK-NEXT: [[C:%.*]] = load double, ptr [[COS_OUT]], align 8
; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
; CHECK-NEXT: ret double [[R]]
;
@@ -90,8 +99,11 @@ define double @intervening_call(double %x, double %y, ptr %errno) {
define double @strict(double %x, double %y, ptr %errno) strictfp {
; CHECK-LABEL: @strict(
-; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]])
-; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[X]])
+; CHECK-NEXT: [[SIN_OUT:%.*]] = alloca double, align 8
+; CHECK-NEXT: [[COS_OUT:%.*]] = alloca double, align 8
+; CHECK-NEXT: call void @sincos(double [[X:%.*]], ptr nonnull [[SIN_OUT]], ptr nonnull [[COS_OUT]])
+; CHECK-NEXT: [[S:%.*]] = load double, ptr [[SIN_OUT]], align 8
+; CHECK-NEXT: [[C:%.*]] = load double, ptr [[COS_OUT]], align 8
; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
; CHECK-NEXT: ret double [[R]]
;
>From a33f77cb997fb6b18589adc316c3c6054cfa6acf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C3=A1vid=20Bolvansk=C3=BD?= <david.bolvansky at gmail.com>
Date: Sun, 4 Oct 2026 02:38:27 +0200
Subject: [PATCH 3/3] [InstCombine] Exercise strict FP call attributes in
sincos test
---
llvm/test/Transforms/InstCombine/sincos-libcalls.ll | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/llvm/test/Transforms/InstCombine/sincos-libcalls.ll b/llvm/test/Transforms/InstCombine/sincos-libcalls.ll
index 59f329701b31c..4f35c283c63e6 100644
--- a/llvm/test/Transforms/InstCombine/sincos-libcalls.ll
+++ b/llvm/test/Transforms/InstCombine/sincos-libcalls.ll
@@ -99,16 +99,13 @@ define double @intervening_call(double %x, double %y, ptr %errno) {
define double @strict(double %x, double %y, ptr %errno) strictfp {
; CHECK-LABEL: @strict(
-; CHECK-NEXT: [[SIN_OUT:%.*]] = alloca double, align 8
-; CHECK-NEXT: [[COS_OUT:%.*]] = alloca double, align 8
-; CHECK-NEXT: call void @sincos(double [[X:%.*]], ptr nonnull [[SIN_OUT]], ptr nonnull [[COS_OUT]])
-; CHECK-NEXT: [[S:%.*]] = load double, ptr [[SIN_OUT]], align 8
-; CHECK-NEXT: [[C:%.*]] = load double, ptr [[COS_OUT]], align 8
+; CHECK-NEXT: [[S:%.*]] = call double @sin(double [[X:%.*]]) #[[ATTR0:[0-9]+]]
+; CHECK-NEXT: [[C:%.*]] = call double @cos(double [[X]]) #[[ATTR0]]
; CHECK-NEXT: [[R:%.*]] = fadd double [[S]], [[C]]
; CHECK-NEXT: ret double [[R]]
;
- %s = call double @sin(double %x)
- %c = call double @cos(double %x)
+ %s = call double @sin(double %x) strictfp
+ %c = call double @cos(double %x) strictfp
%r = fadd double %s, %c
ret double %r
}
More information about the llvm-commits
mailing list