Skip to content

__declspec(noshrinkwrap) support #147397

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Conversation

AdamGlass
Copy link
Contributor

Provide a mechanism to disable the shrinkwrapping process on an individual function via __declspec(noshrinkwrap). This is functionality is sometimes necessary in systems and exception handling code-. Other mechanisms for doing this are -enable-shrink-wrap=false and via llvm::TargetFrameLowering::enableShrinkWrapping() -- neither mechanism is particularly flexible or targeted.

No example of similar documented functionality has been found.

Implementation adds a Clang declspec which drives an LLVM function attribute.
Attribute is consumed by the ShrinkWrap pass.

@llvmbot llvmbot added clang Clang issues not falling into any other category backend:AArch64 clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. llvm:codegen llvm:ir llvm:transforms labels Jul 7, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 7, 2025

@llvm/pr-subscribers-llvm-transforms
@llvm/pr-subscribers-clang-codegen
@llvm/pr-subscribers-clang

@llvm/pr-subscribers-llvm-ir

Author: Adam Glass (AdamGlass)

Changes

Provide a mechanism to disable the shrinkwrapping process on an individual function via __declspec(noshrinkwrap). This is functionality is sometimes necessary in systems and exception handling code-. Other mechanisms for doing this are -enable-shrink-wrap=false and via llvm::TargetFrameLowering::enableShrinkWrapping() -- neither mechanism is particularly flexible or targeted.

No example of similar documented functionality has been found.

Implementation adds a Clang declspec which drives an LLVM function attribute.
Attribute is consumed by the ShrinkWrap pass.


Full diff: https://github.com/llvm/llvm-project/pull/147397.diff

12 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+7)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+8)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+4)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+4)
  • (added) clang/test/CodeGen/declspec-noshrinkwrap.c (+11)
  • (modified) llvm/include/llvm/Bitcode/LLVMBitCodes.h (+1)
  • (modified) llvm/include/llvm/IR/Attributes.td (+3)
  • (modified) llvm/lib/Bitcode/Reader/BitcodeReader.cpp (+2)
  • (modified) llvm/lib/Bitcode/Writer/BitcodeWriter.cpp (+2)
  • (modified) llvm/lib/CodeGen/ShrinkWrap.cpp (+3)
  • (modified) llvm/lib/Transforms/Utils/CodeExtractor.cpp (+1)
  • (added) llvm/test/CodeGen/AArch64/arm64-shrink-wrapping-noshrinkwrap.ll (+106)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 340f439a45bb9..ba2d121bd7cf7 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -5190,3 +5190,10 @@ def NonString : InheritableAttr {
   let Subjects = SubjectList<[Var, Field]>;
   let Documentation = [NonStringDocs];
 }
+
+def NoShrinkWrapping : InheritableAttr {
+  let Spellings = [Declspec<"noshrinkwrap">];
+  let Subjects = SubjectList<[Function]>;
+  let Documentation = [NoShrinkWrappingDocs];
+  let SimpleHandler = 1;
+}
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 43442f177ab7b..7c0be6d4a3f21 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9427,3 +9427,11 @@ diagnostics with code like:
   __attribute__((nonstring)) char NotAStr[3] = "foo"; // Not diagnosed
   }];
 }
+
+def NoShrinkWrappingDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+  The ``__declspec(noshrinkwrap)`` attribute disables the shrinkwrapping
+  process for this function.
+  }];
+}
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index e6d150f7e13d6..5d7d53cdbf3b9 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -2642,6 +2642,10 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D,
   else if (isStackProtectorOn(LangOpts, getTriple(), LangOptions::SSPReq))
     B.addAttribute(llvm::Attribute::StackProtectReq);
 
+  if (D && D->hasAttr<NoShrinkWrappingAttr>()) {
+    B.addAttribute(llvm::Attribute::NoShrinkWrap);
+  }
+
   if (!D) {
     // Non-entry HLSL functions must always be inlined.
     if (getLangOpts().HLSL && !F->hasFnAttribute(llvm::Attribute::NoInline))
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index c5d5d03cc99c7..0b8a367238d23 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7869,6 +7869,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_VTablePointerAuthentication:
     handleVTablePointerAuthentication(S, D, AL);
     break;
+
+  case ParsedAttr::AT_NoShrinkWrapping:
+    handleSimpleAttribute<NoShrinkWrappingAttr>(S, D, AL);
+    break;
   }
 }
 
diff --git a/clang/test/CodeGen/declspec-noshrinkwrap.c b/clang/test/CodeGen/declspec-noshrinkwrap.c
new file mode 100644
index 0000000000000..fee321875c2d3
--- /dev/null
+++ b/clang/test/CodeGen/declspec-noshrinkwrap.c
@@ -0,0 +1,11 @@
+// RUN: %clang_cc1 -fdeclspec -emit-llvm %s -o - | FileCheck %s
+
+// Ensure that declspec results in a llvm function attribute
+__declspec(noshrinkwrap)
+int square(int a) {
+  return a*a;
+}
+
+// CHECK: Function Attrs: {{.*}} noshrinkwrap
+// CHECK-LABEL: @square
+// CHECK: attributes #0 {{.*}} noshrinkwrap
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index dc78eb4164acf..520dd4b7e528e 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -799,6 +799,7 @@ enum AttributeKindCodes {
   ATTR_KIND_SANITIZE_TYPE = 101,
   ATTR_KIND_CAPTURES = 102,
   ATTR_KIND_DEAD_ON_RETURN = 103,
+  ATTR_KIND_NO_SHRINKWRAP = 104,
 };
 
 enum ComdatSelectionKindCodes {
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index 112853965407c..8212be07ba291 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -402,6 +402,9 @@ def MarkedForWindowsSecureHotPatching
 def AllowDirectAccessInHotPatchFunction
     : StrBoolAttr<"allow_direct_access_in_hot_patch_function">;
 
+/// Function should not be shrinkwrapped
+def NoShrinkWrap : EnumAttr<"noshrinkwrap", IntersectPreserve, [FnAttr]>;
+
 /// Target-independent string attributes.
 def LessPreciseFPMAD : StrBoolAttr<"less-precise-fpmad">;
 def NoInfsFPMath : StrBoolAttr<"no-infs-fp-math">;
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index 66ecc69c9874d..3d4cbdf5e7fb0 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -2246,6 +2246,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
     return Attribute::Captures;
   case bitc::ATTR_KIND_DEAD_ON_RETURN:
     return Attribute::DeadOnReturn;
+  case bitc::ATTR_KIND_NO_SHRINKWRAP:
+    return Attribute::NoShrinkWrap;
   }
 }
 
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index 7e0d81ff4b196..a8db8592d411e 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -946,6 +946,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
     return bitc::ATTR_KIND_CAPTURES;
   case Attribute::DeadOnReturn:
     return bitc::ATTR_KIND_DEAD_ON_RETURN;
+  case Attribute::NoShrinkWrap:
+    return bitc::ATTR_KIND_NO_SHRINKWRAP;
   case Attribute::EndAttrKinds:
     llvm_unreachable("Can not encode end-attribute kinds marker.");
   case Attribute::None:
diff --git a/llvm/lib/CodeGen/ShrinkWrap.cpp b/llvm/lib/CodeGen/ShrinkWrap.cpp
index 41e956caa7b34..ce51baba0a691 100644
--- a/llvm/lib/CodeGen/ShrinkWrap.cpp
+++ b/llvm/lib/CodeGen/ShrinkWrap.cpp
@@ -1013,6 +1013,9 @@ PreservedAnalyses ShrinkWrapPass::run(MachineFunction &MF,
 bool ShrinkWrapImpl::isShrinkWrapEnabled(const MachineFunction &MF) {
   const TargetFrameLowering *TFI = MF.getSubtarget().getFrameLowering();
 
+  if (MF.getFunction().hasFnAttribute(Attribute::NoShrinkWrap))
+    return false;
+
   switch (EnableShrinkWrapOpt) {
   case cl::BOU_UNSET:
     return TFI->enableShrinkWrapping(MF) &&
diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
index eacaf42e4e8ba..21bb4877689f9 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -981,6 +981,7 @@ Function *CodeExtractor::constructFunctionDeclaration(
       case Attribute::MustProgress:
       case Attribute::NoProfile:
       case Attribute::SkipProfile:
+      case Attribute::NoShrinkWrap:
         break;
       // These attributes cannot be applied to functions.
       case Attribute::Alignment:
diff --git a/llvm/test/CodeGen/AArch64/arm64-shrink-wrapping-noshrinkwrap.ll b/llvm/test/CodeGen/AArch64/arm64-shrink-wrapping-noshrinkwrap.ll
new file mode 100644
index 0000000000000..119da3ac4f2e9
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/arm64-shrink-wrapping-noshrinkwrap.ll
@@ -0,0 +1,106 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -debugify-and-strip-all-safe %s -o - -mtriple=arm64-apple-ios -enable-shrink-wrap=true -disable-post-ra -frame-pointer=non-leaf | FileCheck %s --check-prefix=ENABLE
+; RUN: llc -debugify-and-strip-all-safe %s -o - -enable-shrink-wrap=false -disable-post-ra -frame-pointer=non-leaf | FileCheck %s --check-prefix=DISABLE
+; RUN: llc -debugify-and-strip-all-safe %s -o - -mtriple=arm64-apple-ios -enable-shrink-wrap=true -disable-post-ra -frame-pointer=non-leaf | FileCheck %s --check-prefix=NOSHRINK
+target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128"
+target triple = "arm64-apple-ios"
+
+; Verify that the attribute 'noshrinkwrap' has the same effect as disabling the
+; shrinkwrapping entirely.
+
+; Known shrinkwrap example: Simple diamond with a call just on one side.
+define i32 @foo(i32 %a, i32 %b) {
+; ENABLE-LABEL: foo:
+; ENABLE:       ; %bb.0:
+; ENABLE-NEXT:    cmp w0, w1
+; ENABLE-NEXT:    b.ge LBB0_2
+; ENABLE-NEXT:  ; %bb.1: ; %true
+; ENABLE-NEXT:    sub sp, sp, #32
+; ENABLE-NEXT:    stp x29, x30, [sp, #16] ; 16-byte Folded Spill
+; ENABLE-NEXT:    add x29, sp, #16
+; ENABLE-NEXT:    .cfi_def_cfa w29, 16
+; ENABLE-NEXT:    .cfi_offset w30, -8
+; ENABLE-NEXT:    .cfi_offset w29, -16
+; ENABLE-NEXT:    stur w0, [x29, #-4]
+; ENABLE-NEXT:    sub x1, x29, #4
+; ENABLE-NEXT:    mov w0, wzr
+; ENABLE-NEXT:    bl _doSomething
+; ENABLE-NEXT:    ldp x29, x30, [sp, #16] ; 16-byte Folded Reload
+; ENABLE-NEXT:    add sp, sp, #32
+; ENABLE-NEXT:  LBB0_2: ; %false
+; ENABLE-NEXT:    ret
+; ENABLE-LABEL: bar
+;
+; DISABLE-LABEL: foo:
+; DISABLE:       ; %bb.0:
+; DISABLE-NEXT:    sub sp, sp, #32
+; DISABLE-NEXT:    stp x29, x30, [sp, #16] ; 16-byte Folded Spill
+; DISABLE-NEXT:    add x29, sp, #16
+; DISABLE-NEXT:    .cfi_def_cfa w29, 16
+; DISABLE-NEXT:    .cfi_offset w30, -8
+; DISABLE-NEXT:    .cfi_offset w29, -16
+; DISABLE-NEXT:    cmp w0, w1
+; DISABLE-NEXT:    b.ge LBB0_2
+; DISABLE-NEXT:  ; %bb.1: ; %true
+; DISABLE-NEXT:    stur w0, [x29, #-4]
+; DISABLE-NEXT:    sub x1, x29, #4
+; DISABLE-NEXT:    mov w0, wzr
+; DISABLE-NEXT:    bl _doSomething
+; DISABLE-NEXT:  LBB0_2: ; %false
+; DISABLE-NEXT:    ldp x29, x30, [sp, #16] ; 16-byte Folded Reload
+; DISABLE-NEXT:    add sp, sp, #32
+; DISABLE-NEXT:    ret
+; DISABLE-LABEL: bar
+  %tmp = alloca i32, align 4
+  %tmp2 = icmp slt i32 %a, %b
+  br i1 %tmp2, label %true, label %false
+
+true:
+  store i32 %a, ptr %tmp, align 4
+  %tmp4 = call i32 @doSomething(i32 0, ptr %tmp)
+  br label %false
+
+false:
+  %tmp.0 = phi i32 [ %tmp4, %true ], [ %a, %0 ]
+  ret i32 %tmp.0
+}
+
+; Same code as above but with 'noshrinkwrap' attribute
+
+define i32 @bar(i32 %a, i32 %b) noshrinkwrap {
+; NOSHRINK-LABEL: bar:
+; NOSHRINK:       ; %bb.0:
+; NOSHRINK-NEXT:    sub sp, sp, #32
+; NOSHRINK-NEXT:    stp x29, x30, [sp, #16] ; 16-byte Folded Spill
+; NOSHRINK-NEXT:    add x29, sp, #16
+; NOSHRINK-NEXT:    .cfi_def_cfa w29, 16
+; NOSHRINK-NEXT:    .cfi_offset w30, -8
+; NOSHRINK-NEXT:    .cfi_offset w29, -16
+; NOSHRINK-NEXT:    cmp w0, w1
+; NOSHRINK-NEXT:    b.ge LBB1_2
+; NOSHRINK-NEXT:  ; %bb.1: ; %true
+; NOSHRINK-NEXT:    stur w0, [x29, #-4]
+; NOSHRINK-NEXT:    sub x1, x29, #4
+; NOSHRINK-NEXT:    mov w0, wzr
+; NOSHRINK-NEXT:    bl _doSomething
+; NOSHRINK-NEXT:  LBB1_2: ; %false
+; NOSHRINK-NEXT:    ldp x29, x30, [sp, #16] ; 16-byte Folded Reload
+; NOSHRINK-NEXT:    add sp, sp, #32
+; NOSHRINK-NEXT:    ret
+  %tmp = alloca i32, align 4
+  %tmp2 = icmp slt i32 %a, %b
+  br i1 %tmp2, label %true, label %false
+
+true:
+  store i32 %a, ptr %tmp, align 4
+  %tmp4 = call i32 @doSomething(i32 0, ptr %tmp)
+  br label %false
+
+false:
+  %tmp.0 = phi i32 [ %tmp4, %true ], [ %a, %0 ]
+  ret i32 %tmp.0
+}
+
+; Function Attrs: optsize
+declare i32 @doSomething(i32, ptr)
+

@efriedma-quic
Copy link
Collaborator

As a practical matter, I don't think LLVM supports shrink-wrapping on Windows at the moment. I mean, I guess it's something someone could theoretically look into at some point, but the interaction with unwind info is complicated.

Can we define what it means to "shrink-wrap" in a way that isn't specifically tied to the shrink-wrapping pass? We need to define what output is legal, not how we got there. For example, if the rule is that we aren't allowed to access non-stack memory before adjusting the stack, that could interact with late instruction scheduling.

It looks like this isn't an existing MSVC feature? If that's the case, why are we using __declspec syntax, as opposed to modern C++ attribute syntax?

@AdamGlass
Copy link
Contributor Author

Change isn't Windows specific -- seems generally useful. You're right that it enshrines the functionality responsible rather than desire 'don't get cute with the prologue'. I'm open to a better name.

re: __declspec() -- there is no compatibility requirement for this choice. MSVC has an undocumented mechanism but it uses intrinsics while still having the same behavior as what's proposed here. I'll look at how to enable the modern attribute style instead.

thanks!

@efriedma-quic
Copy link
Collaborator

This is probably worth an RFC (https://clang.llvm.org/get_involved.html).

The "late instruction scheduling" example is something I'm pretty sure can actually trigger in practice; does prolog/epilog lowering need to insert some kind of barrier instruction?

And the attribute needs to be documented in LangRef.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:AArch64 clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category llvm:codegen llvm:ir llvm:transforms
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants