[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