https://github.com/zyma98 updated 
https://github.com/llvm/llvm-project/pull/208076

>From c5068d72b824729ed4e80aacdc23bca03b0c2689 Mon Sep 17 00:00:00 2001
From: Zhiyao Ma <[email protected]>
Date: Tue, 7 Jul 2026 00:05:24 -0400
Subject: [PATCH 1/2] [clang][ARM] Add -fstack-limit-variable to trap before
 stack overflow

Add a codegen option -fstack-limit-variable=<var>, intended for
microcontroller firmware that wants a cheap runtime guard against
stack overflow. The emitted prologue reads the global <var> (the
lowest address the stack may reach), computes the free stack space as
(SP - <var>), and compares it against the frame size the function is
about to allocate. On overflow the function executes "svc #<n>"
instead of running its body, so an RTOS supervisor-call handler can
deal with the failure. The SVC number defaults to 255 and is
configurable with -fstack-limit-trap-number=<n>. The limit symbol
resolves at link time.

Clang lowers the options to the "stack-limit-variable" and
"stack-limit-trap-number" function attributes. In the backend, the
PrologEpilogInserter pass checks for the attribute and calls a new
target hook, TargetFrameLowering::adjustForStackLimitCheck, which
inserts the target's check sequence ahead of the normal prologue.
The options are target-neutral so other architectures can implement
the hook later. This patch implements the new option for Cortex-M as
the first supported target.

- Thumb2 (thumbv7m / Cortex-M3 and later) materializes the limit with
  movw/movt, using r12 as scratch. Other registers are not saved or
  restored. Frame sizes that do not fit the cmp immediate encoding are
  rounded up to the next encodable value, keeping the check
  conservative.
- Thumb1 (thumbv6m / Cortex-M0) loads the limit address from the
  constant pool and uses r2/r3, preserving whichever is live on entry
  with a push/pop. Large frame sizes are compared via a register
  loaded from the constant pool, so no rounding is needed. The
  transient push writes below SP, so 8 bytes of guard space must be
  reserved below the limit.

This resembles GCC's -fstack-limit-symbol=, with two deliberate
differences. GCC compares SP against the address of the symbol, a
boundary fixed at link time. This new option loads the current value of
the named variable on each function entry, so an RTOS can store a new
limit when it context-switches between tasks with different stacks
(GCC's dynamic alternative, -fstack-limit-register=, permanently
reserves a register instead). And where GCC's check executes a machine
trap instruction that raises a signal, this option traps with a
supervisor call whose number is configurable, so bare-metal firmware
can handle the overflow in an ordinary SVC handler.
---
 clang/include/clang/Basic/CodeGenOptions.def  |   3 +
 clang/include/clang/Basic/CodeGenOptions.h    |   5 +
 clang/include/clang/Options/Options.td        |  15 +
 clang/lib/CodeGen/CGCall.cpp                  |   6 +
 clang/lib/Driver/ToolChains/Clang.cpp         |  33 ++
 clang/test/CodeGen/stack-limit-variable.c     |  29 ++
 clang/test/Driver/stack-limit-variable.c      |  37 ++
 llvm/include/llvm/CodeGen/MachineFunction.h   |   4 +
 .../llvm/CodeGen/TargetFrameLowering.h        |   9 +
 llvm/lib/CodeGen/MachineFunction.cpp          |   5 +
 llvm/lib/CodeGen/PrologEpilogInserter.cpp     |   8 +
 llvm/lib/CodeGen/TargetFrameLoweringImpl.cpp  |  13 +
 llvm/lib/Target/ARM/ARMFrameLowering.cpp      | 317 ++++++++++++++++++
 llvm/lib/Target/ARM/ARMFrameLowering.h        |   2 +
 .../ARM/stack-limit-variable-diagnostics.ll   |  30 ++
 .../ARM/stack-limit-variable-thumb1.ll        | 265 +++++++++++++++
 .../ARM/stack-limit-variable-thumb2.ll        | 148 ++++++++
 17 files changed, 929 insertions(+)
 create mode 100644 clang/test/CodeGen/stack-limit-variable.c
 create mode 100644 clang/test/Driver/stack-limit-variable.c
 create mode 100644 llvm/test/CodeGen/ARM/stack-limit-variable-diagnostics.ll
 create mode 100644 llvm/test/CodeGen/ARM/stack-limit-variable-thumb1.ll
 create mode 100644 llvm/test/CodeGen/ARM/stack-limit-variable-thumb2.ll

diff --git a/clang/include/clang/Basic/CodeGenOptions.def 
b/clang/include/clang/Basic/CodeGenOptions.def
index 49374efbeb28b..25f585e0aa601 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -438,6 +438,9 @@ VALUE_CODEGENOPT(StackProtectorGuardOffset, 32, INT_MAX, 
Benign)
 /// The width to use for the stack protector guard value.
 VALUE_CODEGENOPT(StackProtectorGuardValueWidth, 32, UINT_MAX, Benign)
 
+/// The trap immediate emitted by the -fstack-limit-variable check on overflow.
+VALUE_CODEGENOPT(StackLimitTrapNumber, 32, 255, Benign)
+
 /// Number of path components to strip when emitting checks. (0 == full
 /// filename)
 VALUE_CODEGENOPT(EmitCheckPathComponentsToStrip, 32, 0, Benign)
diff --git a/clang/include/clang/Basic/CodeGenOptions.h 
b/clang/include/clang/Basic/CodeGenOptions.h
index 97d68877467fd..6c6ef1c9124d1 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -526,6 +526,11 @@ class CodeGenOptions : public CodeGenOptionsBase {
   /// Specify a symbol to be the guard value.
   std::string StackProtectorGuardSymbol;
 
+  /// Name of the global variable holding the stack limit for the ARM
+  /// -fstack-limit-variable=<var> stack-limit check prologue. Empty when the
+  /// feature is disabled.
+  std::string StackLimitVariable;
+
   /// Path to ignorelist file specifying which objects
   /// (files, functions) listed for instrumentation by sanitizer
   /// coverage pass should actually not be instrumented.
diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index b2e32c75e5745..be8b0f4622a31 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4466,6 +4466,21 @@ defm stack_clash_protection : 
BoolFOption<"stack-clash-protection",
   NegFlag<SetFalse, [], [ClangOption], "Disable">,
   BothFlags<[], [ClangOption], " stack clash protection">>,
   DocBrief<"Instrument stack allocation to prevent stack clash attacks">;
+def fstack_limit_variable_EQ : Joined<["-"], "fstack-limit-variable=">, 
Group<f_Group>,
+  Visibility<[ClangOption, CC1Option]>,
+  HelpText<"Emit a function prologue that traps when the free stack space "
+           "(SP - <var>) is smaller than the frame this function needs. "
+           "<var> is a global variable holding the stack limit">,
+  MarshallingInfoString<CodeGenOpts<"StackLimitVariable">>,
+  DocBrief<"Emit a function prologue that compares free stack space against 
the "
+           "global named by <var> and executes a trap instruction on "
+           "insufficient space">;
+def fstack_limit_trap_number_EQ : Joined<["-"], "fstack-limit-trap-number=">, 
Group<f_Group>,
+  Visibility<[ClangOption, CC1Option]>,
+  HelpText<"Set the immediate operand of the trap instruction emitted by "
+           "-fstack-limit-variable on stack overflow (e.g. 'svc #<n>' on ARM). 
"
+           "Defaults to 255. Requires -fstack-limit-variable">,
+  MarshallingInfoInt<CodeGenOpts<"StackLimitTrapNumber">, "255">;
 def fstack_protector_strong : Flag<["-"], "fstack-protector-strong">, 
Group<f_Group>,
   HelpText<"Enable stack protectors for some functions vulnerable to stack 
smashing. "
            "Compared to -fstack-protector, this uses a stronger heuristic "
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 0f272eab4e842..7b1866553c07f 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2316,6 +2316,12 @@ static void getTrivialDefaultFunctionAttributes(
       FuncAttrs.addAttribute("backchain");
     if (CodeGenOpts.EnableSegmentedStacks)
       FuncAttrs.addAttribute("split-stack");
+    if (!CodeGenOpts.StackLimitVariable.empty()) {
+      FuncAttrs.addAttribute("stack-limit-variable",
+                             CodeGenOpts.StackLimitVariable);
+      FuncAttrs.addAttribute("stack-limit-trap-number",
+                             llvm::utostr(CodeGenOpts.StackLimitTrapNumber));
+    }
 
     if (CodeGenOpts.SpeculativeLoadHardening)
       FuncAttrs.addAttribute(llvm::Attribute::SpeculativeLoadHardening);
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index a30b01a675b99..fcb11c86add48 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -3824,6 +3824,38 @@ static void RenderSCPOptions(const ToolChain &TC, const 
ArgList &Args,
                     options::OPT_fno_stack_clash_protection);
 }
 
+static void RenderStackLimitVariableOptions(const Driver &D,
+                                            const ArgList &Args,
+                                            ArgStringList &CmdArgs) {
+  // -fstack-limit-variable=<var> names the global holding the stack limit that
+  // the stack-limit check prologue reads. Backends that do not implement the
+  // check reject the resulting function attribute at codegen time. The value
+  // must be a legal symbol name.
+  //
+  // -fstack-limit-trap-number=<n> sets the trap immediate emitted on overflow
+  // and is only meaningful together with -fstack-limit-variable.
+  Arg *Var = Args.getLastArg(options::OPT_fstack_limit_variable_EQ);
+  Arg *Trap = Args.getLastArg(options::OPT_fstack_limit_trap_number_EQ);
+
+  if (Trap && !Var) {
+    D.Diag(diag::err_drv_argument_only_allowed_with)
+        << Trap->getOption().getName() << "-fstack-limit-variable=";
+    return;
+  }
+
+  if (Var) {
+    if (!isValidSymbolName(Var->getValue())) {
+      D.Diag(diag::err_drv_argument_only_allowed_with)
+          << Var->getOption().getName() << "legal symbol name";
+      return;
+    }
+    Var->render(Args, CmdArgs);
+  }
+
+  if (Trap)
+    Trap->render(Args, CmdArgs);
+}
+
 static void RenderTrivialAutoVarInitOptions(const Driver &D,
                                             const ToolChain &TC,
                                             const ArgList &Args,
@@ -7314,6 +7346,7 @@ void Clang::ConstructJob(Compilation &C, const JobAction 
&JA,
 
   RenderSSPOptions(D, TC, Args, CmdArgs, KernelOrKext);
   RenderSCPOptions(TC, Args, CmdArgs);
+  RenderStackLimitVariableOptions(D, Args, CmdArgs);
   RenderTrivialAutoVarInitOptions(D, TC, Args, CmdArgs);
 
   Args.AddLastArg(CmdArgs, options::OPT_fswift_async_fp_EQ);
diff --git a/clang/test/CodeGen/stack-limit-variable.c 
b/clang/test/CodeGen/stack-limit-variable.c
new file mode 100644
index 0000000000000..7f7c13a967f92
--- /dev/null
+++ b/clang/test/CodeGen/stack-limit-variable.c
@@ -0,0 +1,29 @@
+// Check that -fstack-limit-variable=<var> attaches the valued
+// "stack-limit-variable" function attribute (together with the default
+// "stack-limit-trap-number"="255"), that -fstack-limit-trap-number=<n>
+// overrides the trap value, and that both are absent by default.
+
+// The attribute is emitted identically on every architecture the backend
+// check currently supports: Thumb1 (thumbv6m / Cortex-M0) and Thumb2
+// (thumbv7m / Cortex-M3, thumbv7em / Cortex-M4 and later).
+// RUN: %clang_cc1 -triple thumbv6m-none-eabi -emit-llvm -o - %s 
-fstack-limit-variable=__stack_boundary | FileCheck %s 
-check-prefix=DEFAULT-TRAP
+// RUN: %clang_cc1 -triple thumbv7m-none-eabi -emit-llvm -o - %s 
-fstack-limit-variable=__stack_boundary | FileCheck %s 
-check-prefix=DEFAULT-TRAP
+// RUN: %clang_cc1 -triple thumbv7em-none-eabi -emit-llvm -o - %s 
-fstack-limit-variable=__stack_boundary | FileCheck %s 
-check-prefix=DEFAULT-TRAP
+
+// RUN: %clang_cc1 -triple thumbv7em-none-eabi -emit-llvm -o - %s 
-fstack-limit-variable=my_limit | FileCheck %s -check-prefix=CUSTOM-VAR
+// RUN: %clang_cc1 -triple thumbv7em-none-eabi -emit-llvm -o - %s 
-fstack-limit-variable=__stack_boundary -fstack-limit-trap-number=42 | 
FileCheck %s -check-prefix=CUSTOM-TRAP
+// RUN: %clang_cc1 -triple thumbv7em-none-eabi -emit-llvm -o - %s | FileCheck 
%s -check-prefix=NO-ATTR
+
+// DEFAULT-TRAP: define{{.*}} void @foo() #[[ATTR:[0-9]+]]
+// DEFAULT-TRAP: attributes #[[ATTR]] = {{{.*}} 
"stack-limit-trap-number"="255" {{.*}}"stack-limit-variable"="__stack_boundary"
+
+// CUSTOM-VAR: define{{.*}} void @foo() #[[ATTR:[0-9]+]]
+// CUSTOM-VAR: attributes #[[ATTR]] = {{{.*}} "stack-limit-trap-number"="255" 
{{.*}}"stack-limit-variable"="my_limit"
+
+// CUSTOM-TRAP: define{{.*}} void @foo() #[[ATTR:[0-9]+]]
+// CUSTOM-TRAP: attributes #[[ATTR]] = {{{.*}} "stack-limit-trap-number"="42" 
{{.*}}"stack-limit-variable"="__stack_boundary"
+
+// NO-ATTR-NOT: "stack-limit-variable"
+// NO-ATTR-NOT: "stack-limit-trap-number"
+
+void foo(void) {}
diff --git a/clang/test/Driver/stack-limit-variable.c 
b/clang/test/Driver/stack-limit-variable.c
new file mode 100644
index 0000000000000..7ae2f210771b0
--- /dev/null
+++ b/clang/test/Driver/stack-limit-variable.c
@@ -0,0 +1,37 @@
+// Check that -fstack-limit-variable=<var> is forwarded to -cc1, that the last
+// value wins, and that an illegal variable name is rejected.
+
+// RUN: %clang --target=arm-none-eabi -fstack-limit-variable=__stack_boundary 
-### -c %s 2>&1 | FileCheck %s -check-prefix=FORWARD
+// RUN: %clang --target=thumbv7em-none-eabi 
-fstack-limit-variable=__stack_boundary -### -c %s 2>&1 | FileCheck %s 
-check-prefix=FORWARD
+// FORWARD: "-cc1"
+// FORWARD-SAME: "-fstack-limit-variable=__stack_boundary"
+
+// The default is off.
+// RUN: %clang --target=thumbv7em-none-eabi -### -c %s 2>&1 | FileCheck %s 
-check-prefix=DEFAULT
+// DEFAULT-NOT: "-fstack-limit-variable=
+
+// Last value wins.
+// RUN: %clang --target=thumbv7em-none-eabi -fstack-limit-variable=first 
-fstack-limit-variable=second -### -c %s 2>&1 | FileCheck %s -check-prefix=LAST
+// LAST-NOT: "-fstack-limit-variable=first"
+// LAST: "-fstack-limit-variable=second"
+
+// Illegal symbol names are rejected.
+// RUN: not %clang --target=thumbv7em-none-eabi -fstack-limit-variable= -c %s 
2>&1 | FileCheck %s -check-prefix=INVALID
+// RUN: not %clang --target=thumbv7em-none-eabi -fstack-limit-variable=1bad -c 
%s 2>&1 | FileCheck %s -check-prefix=INVALID
+// INVALID: error: invalid argument 'fstack-limit-variable=' only allowed with 
'legal symbol name'
+
+// -fstack-limit-trap-number=<n> is forwarded alongside the variable.
+// RUN: %clang --target=thumbv7em-none-eabi 
-fstack-limit-variable=__stack_boundary -fstack-limit-trap-number=42 -### -c %s 
2>&1 | FileCheck %s -check-prefix=TRAP-FORWARD
+// TRAP-FORWARD: "-cc1"
+// TRAP-FORWARD-SAME: "-fstack-limit-variable=__stack_boundary"
+// TRAP-FORWARD-SAME: "-fstack-limit-trap-number=42"
+
+// -fstack-limit-trap-number without -fstack-limit-variable is an error.
+// RUN: not %clang --target=thumbv7em-none-eabi -fstack-limit-trap-number=42 
-c %s 2>&1 | FileCheck %s -check-prefix=TRAP-ALONE
+// TRAP-ALONE: error: invalid argument 'fstack-limit-trap-number=' only 
allowed with '-fstack-limit-variable='
+
+// Non-integer trap values are rejected when cc1 parses the option.
+// RUN: not %clang --target=thumbv7em-none-eabi 
-fstack-limit-variable=__stack_boundary -fstack-limit-trap-number=foo -c %s 
2>&1 | FileCheck %s -check-prefix=TRAP-INVALID
+// TRAP-INVALID: error: invalid integral value 'foo' in 
'-fstack-limit-trap-number=foo'
+
+int foo(void) { return 0; }
diff --git a/llvm/include/llvm/CodeGen/MachineFunction.h 
b/llvm/include/llvm/CodeGen/MachineFunction.h
index 70b82bcd96b11..be6e11ccabcab 100644
--- a/llvm/include/llvm/CodeGen/MachineFunction.h
+++ b/llvm/include/llvm/CodeGen/MachineFunction.h
@@ -921,6 +921,10 @@ class LLVM_ABI MachineFunction {
   /// Should we be emitting segmented stack stuff for the function
   bool shouldSplitStack() const;
 
+  /// Does this function request a prologue stack-limit check, i.e. carry the
+  /// "stack-limit-variable" attribute (see -fstack-limit-variable)?
+  bool hasStackLimitCheck() const;
+
   /// getNumBlockIDs - Return the number of MBB ID's allocated.
   unsigned getNumBlockIDs() const { return (unsigned)MBBNumbering.size(); }
 
diff --git a/llvm/include/llvm/CodeGen/TargetFrameLowering.h 
b/llvm/include/llvm/CodeGen/TargetFrameLowering.h
index 66b1cc137e06c..89df8bd064bd5 100644
--- a/llvm/include/llvm/CodeGen/TargetFrameLowering.h
+++ b/llvm/include/llvm/CodeGen/TargetFrameLowering.h
@@ -254,6 +254,15 @@ class LLVM_ABI TargetFrameLowering {
   virtual void adjustForHiPEPrologue(MachineFunction &MF,
                                      MachineBasicBlock &PrologueMBB) const {}
 
+  /// Prepend a stack-limit check to the prologue of functions that carry the
+  /// "stack-limit-variable" attribute (see -fstack-limit-variable). The check
+  /// traps when the incoming stack pointer leaves too little room for this
+  /// function's frame. Targets that implement the feature override this
+  /// method. The default implementation rejects the request with an
+  /// unsupported-feature diagnostic, so the check is never silently dropped
+  /// on targets that do not implement it.
+  virtual void adjustForStackLimitCheck(MachineFunction &MF) const;
+
   /// spillCalleeSavedRegisters - Issues instruction(s) to spill all callee
   /// saved registers and returns true if it isn't possible / profitable to do
   /// so by issuing a series of store instructions via
diff --git a/llvm/lib/CodeGen/MachineFunction.cpp 
b/llvm/lib/CodeGen/MachineFunction.cpp
index bbc5041a9166d..b0e9b9809fea1 100644
--- a/llvm/lib/CodeGen/MachineFunction.cpp
+++ b/llvm/lib/CodeGen/MachineFunction.cpp
@@ -353,6 +353,11 @@ bool MachineFunction::shouldSplitStack() const {
   return getFunction().hasFnAttribute("split-stack");
 }
 
+/// Does this function request a prologue stack-limit check?
+bool MachineFunction::hasStackLimitCheck() const {
+  return getFunction().hasFnAttribute("stack-limit-variable");
+}
+
 Align MachineFunction::getPreferredAlignment() const {
   Align PrefAlignment;
 
diff --git a/llvm/lib/CodeGen/PrologEpilogInserter.cpp 
b/llvm/lib/CodeGen/PrologEpilogInserter.cpp
index 54a77524add94..193f904df329d 100644
--- a/llvm/lib/CodeGen/PrologEpilogInserter.cpp
+++ b/llvm/lib/CodeGen/PrologEpilogInserter.cpp
@@ -1202,6 +1202,14 @@ void PEIImpl::insertPrologEpilogCode(MachineFunction 
&MF) {
   if (MF.getFunction().getCallingConv() == CallingConv::HiPE)
     for (MachineBasicBlock *SaveBlock : SaveBlocks)
       TFI.adjustForHiPEPrologue(MF, *SaveBlock);
+
+  // Prepend a stack-limit check to the entry prologue when the function
+  // requests one via -fstack-limit-variable. Like segmented stacks this adds a
+  // conditional check ahead of the normal prologue, but it traps instead of
+  // growing the stack. (Naked functions never reach this point, so they are
+  // left uninstrumented.)
+  if (MF.hasStackLimitCheck())
+    TFI.adjustForStackLimitCheck(MF);
 }
 
 /// insertZeroCallUsedRegs - Zero out call used registers.
diff --git a/llvm/lib/CodeGen/TargetFrameLoweringImpl.cpp 
b/llvm/lib/CodeGen/TargetFrameLoweringImpl.cpp
index 06f1676ff72a0..2bb6a24386af7 100644
--- a/llvm/lib/CodeGen/TargetFrameLoweringImpl.cpp
+++ b/llvm/lib/CodeGen/TargetFrameLoweringImpl.cpp
@@ -18,8 +18,10 @@
 #include "llvm/CodeGen/TargetInstrInfo.h"
 #include "llvm/CodeGen/TargetSubtargetInfo.h"
 #include "llvm/IR/Attributes.h"
+#include "llvm/IR/DiagnosticInfo.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/InstrTypes.h"
+#include "llvm/IR/LLVMContext.h"
 #include "llvm/MC/MCAsmInfo.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Target/TargetMachine.h"
@@ -29,6 +31,17 @@ using namespace llvm;
 
 TargetFrameLowering::~TargetFrameLowering() = default;
 
+void TargetFrameLowering::adjustForStackLimitCheck(MachineFunction &MF) const {
+  // Reaching the generic implementation means a function requested a
+  // stack-limit check (via -fstack-limit-variable) but the current target does
+  // not implement one. Because the feature is about stack-overflow safety,
+  // surface that as a hard error rather than silently leaving the function
+  // unprotected.
+  const Function &F = MF.getFunction();
+  F.getContext().diagnose(DiagnosticInfoUnsupported(
+      F, "-fstack-limit-variable is not supported for this target"));
+}
+
 bool TargetFrameLowering::enableCalleeSaveSkip(const MachineFunction &MF) 
const {
   assert(MF.getFunction().hasFnAttribute(Attribute::NoReturn) &&
          MF.getFunction().hasFnAttribute(Attribute::NoUnwind) &&
diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp 
b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index 7ccd2ad4aa8c9..2c1ffac074348 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -146,6 +146,7 @@
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/MathExtras.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Target/TargetMachine.h"
 #include "llvm/Target/TargetOptions.h"
@@ -3734,3 +3735,319 @@ void ARMFrameLowering::adjustForSegmentedStacks(
   MF.verify();
 #endif
 }
+
+/// Return the smallest value >= \p StackSize that is encodable as a Thumb2
+/// "modified immediate" so it can be used directly as the operand of a CMP.
+/// Rounding the required size up is conservative. It can only make the check
+/// trap slightly earlier, never miss a genuine overflow.
+static unsigned getEncodableRequiredSize(uint64_t StackSize) {
+  unsigned V = StackSize > 0xFFFFFFFFULL ? 0xFFFFFFFFU
+                                         : static_cast<unsigned>(StackSize);
+  // Anything already encodable as a Thumb2 modified immediate needs no
+  // rounding (rotated 8-bit values and byte-splat patterns).
+  if (ARM_AM::getT2SOImmVal(V) != -1)
+    return V;
+
+  // Otherwise keep only the top 8 significant bits and round the rest up. The
+  // result is a value of the form (0x80..0xFF) << s, which can be encoded as a
+  // Thumb2 modified immediate, and overshoots StackSize by less than 1/256 of
+  // the original value.
+  unsigned Shift = Log2_32(V) - 7; // V > 0xFF here, so Log2_32(V) >= 8.
+  uint64_t Mask = (UINT64_C(1) << Shift) - 1;
+  uint64_t Rounded = (static_cast<uint64_t>(V) + Mask) & ~Mask;
+
+  // A carry out of the top 8 bits (e.g. they were all ones) can push the 
result
+  // past 32 bits; that only happens for absurd multi-gigabyte frames, so clamp
+  // to the all-ones splat, the largest representable value.
+  if (Rounded > 0xFFFFFFFFULL)
+    return 0xFFFFFFFFU;
+
+  assert(ARM_AM::getT2SOImmVal(static_cast<unsigned>(Rounded)) != -1 &&
+         "rounded stack size is not a Thumb2 modified immediate");
+  return static_cast<unsigned>(Rounded);
+}
+
+// Implements -fstack-limit-variable=<var> for Thumb (Cortex-M) targets. For a
+// function carrying the "stack-limit-variable"="<var>" attribute this prepends
+// a prologue sequence that:
+//
+//   1. Reads the global variable named by the attribute (the lowest address
+//      the stack is allowed to reach).
+//   2. Computes the currently free stack space as (SP - <var>).
+//   3. Compares that against the stack size required by this function's frame.
+//   4. If there is enough space the function body runs as usual; otherwise it
+//      executes "svc #<n>", where <n> is the trap immediate selected by
+//      -fstack-limit-trap-number (default 255).
+//
+// On Thumb2 (thumbv7m / Cortex-M3, thumbv7em / Cortex-M4 and later) the
+// address is materialized with movw/movt and the arithmetic uses the wide
+// encodings and IP (r12) as scratch, for
+// -fstack-limit-variable=__stack_boundary:
+//
+//     movw r12, :lower16:__stack_boundary
+//     movt r12, :upper16:__stack_boundary
+//     ldr  r12, [r12]          ; r12 = __stack_boundary (limit)
+//     sub  r12, sp, r12        ; r12 = free space = SP - limit
+//     cmp  r12, #frameSize
+//     bhs  .Lbody              ; enough space -> run the body
+//     svc  #<n>                ; insufficient space -> trap
+//   .Lbody:
+//     <original prologue/body>
+//
+// r12 (IP) is a call-clobbered scratch register that is never live on entry
+// under AAPCS, so the Thumb2 sequence needs no save/restore.
+//
+// Thumb1 (thumbv6m / Cortex-M0) has neither movw/movt nor the wide encodings,
+// so the Thumb1 sequence loads the limit's address from the constant pool and
+// does the arithmetic in two low registers. It uses r2 and r3: under AAPCS the
+// integer argument registers are assigned r0, r1, r2, r3 in order, so r2/r3 
are
+// the argument registers least likely to hold an incoming value. Whichever of
+// them is dead on entry is used directly; whichever still carries a live
+// argument is preserved with push/pop around the check (r12 cannot be used
+// instead because Thumb1 low-register instructions cannot address it):
+//
+//     push {r2, r3}?             ; only the scratch regs that are live on 
entry
+//     mov  r3, sp
+//     ldr  r2, =__stack_boundary ; via a PC-relative constant pool entry
+//     ldr  r2, [r2]              ; r2 = __stack_boundary (limit)
+//     subs r3, r3, r2            ; r3 = free space = SP - limit
+//     cmp  r3, #frameSize        ; (large frames: cmp against r2)
+//     pop  {r2, r3}?
+//     bhs  .Lbody
+//     svc  #<n>
+//   .Lbody:
+//     <original prologue/body>
+//
+// The push is a transient write *below* SP: preserving one or two registers
+// stores 4 or 8 bytes into [SP-8, SP) before the comparison runs. If SP has
+// already reached (or is within 8 bytes of) the limit, that store lands below
+// __stack_boundary. The pass therefore requires the 8 bytes immediately below
+// __stack_boundary to be safe to write: whoever defines the limit must leave 
at
+// least 8 bytes of reserved (guard) space between __stack_boundary and the
+// true, unusable end of the stack. Those bytes are only touched transiently,
+// and they are popped again before the trap, so an 8-byte reservation is 
enough
+// no matter how many registers are preserved. (The Thumb2 path uses r12 and
+// writes no memory, so it needs no such reservation.)
+//
+// Because that same spill lowers SP before SP is read, the Thumb1 free-space
+// estimate is also low by 4 bytes per preserved register (0, 4 or 8). The 
check
+// stays conservative and can only trap slightly early, never miss a real
+// overflow. Either way the only registers disturbed on the fall-through path
+// are the dead scratch registers and the condition flags, so all live argument
+// and callee-saved registers reach the original prologue untouched.
+void ARMFrameLowering::adjustForStackLimitCheck(MachineFunction &MF) const {
+  const Function &F = MF.getFunction();
+  const ARMSubtarget &ST = MF.getSubtarget<ARMSubtarget>();
+
+  // The check sequence is implemented for Thumb only. Thumb2 (thumbv7m /
+  // Cortex-M3, thumbv7em / Cortex-M4 and later) uses movw/movt and the wide
+  // encodings, while Thumb1 (thumbv6m / Cortex-M0) loads the symbol address
+  // from the constant pool and uses low-register scratch. ARM (A32) mode is
+  // not supported.
+  if (!ST.isThumb()) {
+    report_fatal_error("-fstack-limit-variable is not supported for ARM (A32) "
+                       "code. Only Thumb (Cortex-M) targets are supported");
+  }
+
+  // The Thumb1 sequence forms the symbol address through the constant pool.
+  // Execute-only targets have no constant pool (and no movw/movt), so there is
+  // no way to materialize it.
+  if (!ST.isThumb2() && ST.genExecuteOnly()) {
+    report_fatal_error("-fstack-limit-variable is not supported for "
+                       "execute-only ARMv6-M (Thumb1) code, which has no "
+                       "constant pool to hold the limit address");
+  }
+
+  // The attribute value is the name of the global holding the stack limit.
+  StringRef LimitVar =
+      F.getFnAttribute("stack-limit-variable").getValueAsString();
+  if (LimitVar.empty()) {
+    report_fatal_error("'stack-limit-variable' attribute must name a non-empty 
"
+                       "global variable");
+  }
+
+  // The trap immediate is configurable via -fstack-limit-trap-number. When the
+  // attribute is absent the default svc number is used.
+  unsigned TrapNumber = 255;
+  if (F.hasFnAttribute("stack-limit-trap-number")) {
+    StringRef TrapStr =
+        F.getFnAttribute("stack-limit-trap-number").getValueAsString();
+    unsigned Parsed;
+    if (TrapStr.getAsInteger(0, Parsed) || Parsed > 255) {
+      report_fatal_error(
+          "'stack-limit-trap-number' value '" + Twine(TrapStr) +
+          "' is not a valid supervisor-call number. Expected an integer in the 
"
+          "range 0-255");
+    }
+    TrapNumber = Parsed;
+  }
+
+  MachineFrameInfo &MFI = MF.getFrameInfo();
+  const uint64_t StackSize = MFI.getStackSize();
+
+  // No frame of our own means nothing to check.
+  if (StackSize == 0)
+    return;
+
+  const TargetInstrInfo &TII = *ST.getInstrInfo();
+
+  MachineBasicBlock *OrigEntry = &MF.front();
+  const DebugLoc DL; // Prologue instructions carry no source location.
+
+  // CheckMBB:  materialize/compare and branch over the trap.
+  // TrapMBB:   svc #<n> on insufficient stack.
+  // Layout:    CheckMBB -> TrapMBB -> OrigEntry, with CheckMBB conditionally
+  //            branching straight to OrigEntry when there is enough space.
+  MachineBasicBlock *CheckMBB = MF.CreateMachineBasicBlock();
+  MachineBasicBlock *TrapMBB = MF.CreateMachineBasicBlock();
+
+  MachineFunction::iterator EntryIt = OrigEntry->getIterator();
+  MF.insert(EntryIt, CheckMBB);
+  MF.insert(EntryIt, TrapMBB);
+
+  // The function's incoming live registers now enter through CheckMBB and flow
+  // unchanged into the original entry block.
+  for (const auto &LI : OrigEntry->liveins()) {
+    CheckMBB->addLiveIn(LI.PhysReg, LI.LaneMask);
+    TrapMBB->addLiveIn(LI.PhysReg, LI.LaneMask);
+  }
+
+  CheckMBB->addSuccessor(OrigEntry);
+  CheckMBB->addSuccessor(TrapMBB);
+  TrapMBB->addSuccessor(OrigEntry);
+
+  if (ST.isThumb2()) {
+    // ---- Thumb2 (thumbv7m / Cortex-M3, thumbv7em / Cortex-M4 and later) ----
+    // The free space is computed in IP (r12), a call-clobbered scratch 
register
+    // that is never live on entry under AAPCS, so nothing needs to be saved.
+    const Register Scratch = ARM::R12;
+    const unsigned RequiredSize = getEncodableRequiredSize(StackSize);
+
+    const char *LimitSym = MF.createExternalSymbolName(LimitVar);
+
+    // movw r12, :lower16:<var>
+    BuildMI(CheckMBB, DL, TII.get(ARM::t2MOVi16), Scratch)
+        .addExternalSymbol(LimitSym, ARMII::MO_LO16)
+        .add(predOps(ARMCC::AL));
+    // movt r12, :upper16:<var>
+    BuildMI(CheckMBB, DL, TII.get(ARM::t2MOVTi16), Scratch)
+        .addReg(Scratch)
+        .addExternalSymbol(LimitSym, ARMII::MO_HI16)
+        .add(predOps(ARMCC::AL));
+    // ldr r12, [r12]   ; r12 = value of <var> (the stack limit)
+    BuildMI(CheckMBB, DL, TII.get(ARM::t2LDRi12), Scratch)
+        .addReg(Scratch)
+        .addImm(0)
+        .add(predOps(ARMCC::AL));
+    // sub r12, sp, r12 ; r12 = SP - limit = free stack space
+    BuildMI(CheckMBB, DL, TII.get(ARM::t2SUBrr), Scratch)
+        .addReg(ARM::SP)
+        .addReg(Scratch)
+        .add(predOps(ARMCC::AL))
+        .add(condCodeOp());
+    // cmp r12, #frameSize
+    BuildMI(CheckMBB, DL, TII.get(ARM::t2CMPri))
+        .addReg(Scratch)
+        .addImm(RequiredSize)
+        .add(predOps(ARMCC::AL));
+  } else {
+    // ---- Thumb1 (thumbv6m / Cortex-M0) ----
+    // ARMv6-M has no movw/movt or wide encodings, so the limit's address is
+    // loaded from the constant pool and the arithmetic uses two low registers.
+    // We use r2 and r3. Under AAPCS the integer argument registers are 
assigned
+    // r0, r1, r2, r3 in order, so r2/r3 are the argument registers least 
likely
+    // to carry an incoming value. Whichever of them is dead on entry is
+    // clobbered directly. Whichever still holds a live argument is preserved
+    // with push/pop around the check. (r2/r3 are low registers, so the Thumb1
+    // low-register instructions can address them. r12 cannot be used here.)
+    const Register S0 = ARM::R2; // &limit -> limit -> (required size)
+    const Register S1 = ARM::R3; // sp -> free space
+    const unsigned RawSize = StackSize > 0xFFFFFFFFULL
+                                 ? 0xFFFFFFFFU
+                                 : static_cast<unsigned>(StackSize);
+
+    // Preserve whichever scratch registers are still carrying an incoming
+    // argument (i.e. are live on entry). If liveness is not being tracked we
+    // cannot prove they are dead, so conservatively preserve both.
+    const bool TracksLiveness = MF.getProperties().hasTracksLiveness();
+    SmallVector<Register, 2> SaveRegs;
+    for (Register R : {S0, S1})
+      if (!TracksLiveness || CheckMBB->isLiveIn(R))
+        SaveRegs.push_back(R);
+
+    // push {<live scratch regs>}  ; only those holding a live argument. This
+    // stores up to 8 bytes into [SP-8, SP) before the limit is checked, so the
+    // 8 bytes just below the limit must be reserved/safe to write (see the
+    // comment above). The spill also lowers SP before it is read below, so the
+    // free-space estimate is low by 4 bytes per preserved register (0, 4 or 
8).
+    // The check stays conservative and can never miss a real overflow.
+    if (!SaveRegs.empty()) {
+      MachineInstrBuilder Push =
+          BuildMI(CheckMBB, DL, TII.get(ARM::tPUSH)).add(predOps(ARMCC::AL));
+      for (Register R : SaveRegs)
+        Push.addReg(R);
+    }
+    // mov r3, sp
+    BuildMI(CheckMBB, DL, TII.get(ARM::tMOVr), S1)
+        .addReg(ARM::SP)
+        .add(predOps(ARMCC::AL));
+    // ldr r2, =<var>   ; r2 = &<var> via a PC-relative constant pool entry
+    ARMFunctionInfo *AFI = MF.getInfo<ARMFunctionInfo>();
+    unsigned PCLabelId = AFI->createPICLabelUId();
+    ARMConstantPoolValue *CPV =
+        ARMConstantPoolSymbol::Create(F.getContext(), LimitVar, PCLabelId, 0);
+    unsigned CPI = MF.getConstantPool()->getConstantPoolIndex(CPV, Align(4));
+    BuildMI(CheckMBB, DL, TII.get(ARM::tLDRpci), S0)
+        .addConstantPoolIndex(CPI)
+        .add(predOps(ARMCC::AL));
+    // ldr r2, [r2]     ; r2 = value of <var> (the stack limit)
+    BuildMI(CheckMBB, DL, TII.get(ARM::tLDRi), S0)
+        .addReg(S0)
+        .addImm(0)
+        .add(predOps(ARMCC::AL));
+    // subs r3, r3, r2  ; r3 = SP - limit = free stack space
+    BuildMI(CheckMBB, DL, TII.get(ARM::tSUBrr), S1)
+        .add(condCodeOp())
+        .addReg(S1)
+        .addReg(S0)
+        .add(predOps(ARMCC::AL));
+    // cmp r3, #frameSize for small frames; for larger ones load the required
+    // size into r2 from the constant pool and compare registers.
+    if (RawSize <= 255) {
+      BuildMI(CheckMBB, DL, TII.get(ARM::tCMPi8))
+          .addReg(S1)
+          .addImm(RawSize)
+          .add(predOps(ARMCC::AL));
+    } else {
+      MachineBasicBlock::iterator It = CheckMBB->end();
+      ST.getRegisterInfo()->emitLoadConstPool(*CheckMBB, It, DL, S0, 0,
+                                              RawSize);
+      BuildMI(CheckMBB, DL, TII.get(ARM::tCMPr))
+          .addReg(S1)
+          .addReg(S0)
+          .add(predOps(ARMCC::AL));
+    }
+    // pop {<live scratch regs>}   ; restore (does not affect the flags)
+    if (!SaveRegs.empty()) {
+      MachineInstrBuilder Pop =
+          BuildMI(CheckMBB, DL, TII.get(ARM::tPOP)).add(predOps(ARMCC::AL));
+      for (Register R : SaveRegs)
+        Pop.addReg(R);
+    }
+  }
+
+  // bhs OrigEntry    ; free >= required (unsigned) -> run the body
+  BuildMI(CheckMBB, DL, TII.get(ARM::tBcc))
+      .addMBB(OrigEntry)
+      .addImm(ARMCC::HS)
+      .addReg(ARM::CPSR);
+
+  // svc #<n>         ; insufficient stack space
+  BuildMI(TrapMBB, DL, TII.get(ARM::tSVC))
+      .addImm(TrapNumber)
+      .add(predOps(ARMCC::AL));
+
+#ifdef EXPENSIVE_CHECKS
+  MF.verify();
+#endif
+}
diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.h 
b/llvm/lib/Target/ARM/ARMFrameLowering.h
index 7021af0d9fbdd..9dc038ab48e95 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.h
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.h
@@ -71,6 +71,8 @@ class ARMFrameLowering : public TargetFrameLowering {
   void adjustForSegmentedStacks(MachineFunction &MF,
                                 MachineBasicBlock &MBB) const override;
 
+  void adjustForStackLimitCheck(MachineFunction &MF) const override;
+
   /// Returns true if the target will correctly handle shrink wrapping.
   bool enableShrinkWrapping(const MachineFunction &MF) const override;
 
diff --git a/llvm/test/CodeGen/ARM/stack-limit-variable-diagnostics.ll 
b/llvm/test/CodeGen/ARM/stack-limit-variable-diagnostics.ll
new file mode 100644
index 0000000000000..2872b1da4740f
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/stack-limit-variable-diagnostics.ll
@@ -0,0 +1,30 @@
+; Because the -fstack-limit-variable stack check guards against stack overflow,
+; a request the backend cannot honour is reported rather than silently dropped:
+; an unsupported target or a malformed attribute is a hard error.
+
+; RUN: rm -rf %t && split-file %s %t
+; RUN: not --crash llc -mtriple=armv7-none-eabi %t/a32.ll -o /dev/null 2>&1 | 
FileCheck %s --check-prefix=A32
+; RUN: not --crash llc -mtriple=thumbv6m-none-eabi -mattr=+execute-only 
%t/xo.ll -o /dev/null 2>&1 | FileCheck %s --check-prefix=XO
+; RUN: not --crash llc -mtriple=thumbv7m-none-eabi %t/empty.ll -o /dev/null 
2>&1 | FileCheck %s --check-prefix=EMPTY
+; RUN: not --crash llc -mtriple=thumbv7m-none-eabi %t/badtrap.ll -o /dev/null 
2>&1 | FileCheck %s --check-prefix=BADTRAP
+
+; A32: LLVM ERROR: {{.*}}-fstack-limit-variable is not supported for ARM (A32) 
code. Only Thumb (Cortex-M) targets are supported
+; XO: LLVM ERROR: {{.*}}-fstack-limit-variable is not supported for 
execute-only ARMv6-M (Thumb1) code
+; EMPTY: LLVM ERROR: {{.*}}'stack-limit-variable' attribute must name a 
non-empty global variable
+; BADTRAP: LLVM ERROR: {{.*}}'stack-limit-trap-number' value '999' is not a 
valid supervisor-call number. Expected an integer in the range 0-255
+
+;--- a32.ll
+define void @f() #0 { ret void }
+attributes #0 = { "stack-limit-variable"="__stack_boundary" }
+
+;--- xo.ll
+define void @f() #0 { ret void }
+attributes #0 = { "stack-limit-variable"="__stack_boundary" }
+
+;--- empty.ll
+define void @f() #0 { ret void }
+attributes #0 = { "stack-limit-variable"="" }
+
+;--- badtrap.ll
+define void @f() #0 { ret void }
+attributes #0 = { "stack-limit-variable"="__stack_boundary" 
"stack-limit-trap-number"="999" }
diff --git a/llvm/test/CodeGen/ARM/stack-limit-variable-thumb1.ll 
b/llvm/test/CodeGen/ARM/stack-limit-variable-thumb1.ll
new file mode 100644
index 0000000000000..ab256fbdad036
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/stack-limit-variable-thumb1.ll
@@ -0,0 +1,265 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py 
UTC_ARGS: --version 6
+; RUN: llc -mtriple=thumbv6m-none-eabi -verify-machineinstrs < %s | FileCheck 
%s
+
+; The -fstack-limit-variable=<var> prologue is emitted only for functions that
+; carry the "stack-limit-variable"="<var>" attribute and have a non-empty stack
+; frame. It loads <var>, computes the free space (sp - limit), compares it
+; against the frame size and traps with "svc #255" when it is too small.
+; Thumb1 has no movw/movt or wide encodings, so the sequence loads the limit's
+; address from the constant pool and does the arithmetic in two low registers
+; (r2/r3). Under AAPCS arguments are assigned r0, r1, r2, r3 in order, so
+; r2/r3 are the argument registers least likely to be live on entry. Each is
+; used directly if dead and preserved with push/pop only if it still carries a
+; live argument.
+
+declare void @sink(ptr)
+
+; A small frame: the required size fits in the 8-bit "cmp" immediate.
+define void @needs_check() #0 {
+; CHECK-LABEL: needs_check:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    mov r3, sp
+; CHECK-NEXT:    ldr r2, .LCPI0_0
+; CHECK-NEXT:    ldr r2, [r2]
+; CHECK-NEXT:    sub r3, r3, r2
+; CHECK-NEXT:    cmp r3, #72
+; CHECK-NEXT:    bhs .LBB0_2
+; CHECK-NEXT:  @ %bb.1:
+; CHECK-NEXT:    svc #255
+; CHECK-NEXT:  .LBB0_2: @ %entry
+; CHECK-NEXT:    .save {r7, lr}
+; CHECK-NEXT:    push {r7, lr}
+; CHECK-NEXT:    .pad #64
+; CHECK-NEXT:    sub sp, #64
+; CHECK-NEXT:    mov r0, sp
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    add sp, #64
+; CHECK-NEXT:    pop {r7, pc}
+; CHECK-NEXT:    .p2align 2
+; CHECK-NEXT:  @ %bb.3:
+; CHECK-NEXT:  .LCPI0_0:
+; CHECK-NEXT:    .long __stack_boundary
+entry:
+  %buf = alloca [64 x i8], align 1
+  call void @sink(ptr %buf)
+  ret void
+}
+
+; A large frame: the required size is materialized from the constant pool and
+; compared register-to-register.
+define void @large_frame() #0 {
+; CHECK-LABEL: large_frame:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    mov r3, sp
+; CHECK-NEXT:    ldr r2, .LCPI1_0
+; CHECK-NEXT:    ldr r2, [r2]
+; CHECK-NEXT:    sub r3, r3, r2
+; CHECK-NEXT:    ldr r2, .LCPI1_1
+; CHECK-NEXT:    cmp r3, r2
+; CHECK-NEXT:    bhs .LBB1_2
+; CHECK-NEXT:  @ %bb.1:
+; CHECK-NEXT:    svc #255
+; CHECK-NEXT:  .LBB1_2: @ %entry
+; CHECK-NEXT:    .save {r4, r5, r6, lr}
+; CHECK-NEXT:    push {r4, r5, r6, lr}
+; CHECK-NEXT:    .pad #508
+; CHECK-NEXT:    sub sp, #508
+; CHECK-NEXT:    .pad #508
+; CHECK-NEXT:    sub sp, #508
+; CHECK-NEXT:    .pad #8
+; CHECK-NEXT:    sub sp, #8
+; CHECK-NEXT:    mov r0, sp
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    add sp, #508
+; CHECK-NEXT:    add sp, #508
+; CHECK-NEXT:    add sp, #8
+; CHECK-NEXT:    pop {r4, r5, r6, pc}
+; CHECK-NEXT:    .p2align 2
+; CHECK-NEXT:  @ %bb.3:
+; CHECK-NEXT:  .LCPI1_0:
+; CHECK-NEXT:    .long __stack_boundary
+; CHECK-NEXT:  .LCPI1_1:
+; CHECK-NEXT:    .long 1040 @ 0x410
+entry:
+  %buf = alloca [1024 x i8], align 1
+  call void @sink(ptr %buf)
+  ret void
+}
+
+; The variable name comes from the attribute.
+define void @custom_var() #1 {
+; CHECK-LABEL: custom_var:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    mov r3, sp
+; CHECK-NEXT:    ldr r2, .LCPI2_0
+; CHECK-NEXT:    ldr r2, [r2]
+; CHECK-NEXT:    sub r3, r3, r2
+; CHECK-NEXT:    cmp r3, #72
+; CHECK-NEXT:    bhs .LBB2_2
+; CHECK-NEXT:  @ %bb.1:
+; CHECK-NEXT:    svc #255
+; CHECK-NEXT:  .LBB2_2: @ %entry
+; CHECK-NEXT:    .save {r7, lr}
+; CHECK-NEXT:    push {r7, lr}
+; CHECK-NEXT:    .pad #64
+; CHECK-NEXT:    sub sp, #64
+; CHECK-NEXT:    mov r0, sp
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    add sp, #64
+; CHECK-NEXT:    pop {r7, pc}
+; CHECK-NEXT:    .p2align 2
+; CHECK-NEXT:  @ %bb.3:
+; CHECK-NEXT:  .LCPI2_0:
+; CHECK-NEXT:    .long my_limit
+entry:
+  %buf = alloca [64 x i8], align 1
+  call void @sink(ptr %buf)
+  ret void
+}
+
+; The trap immediate is taken from "stack-limit-trap-number": svc #42.
+define void @custom_trap() #2 {
+; CHECK-LABEL: custom_trap:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    mov r3, sp
+; CHECK-NEXT:    ldr r2, .LCPI3_0
+; CHECK-NEXT:    ldr r2, [r2]
+; CHECK-NEXT:    sub r3, r3, r2
+; CHECK-NEXT:    cmp r3, #72
+; CHECK-NEXT:    bhs .LBB3_2
+; CHECK-NEXT:  @ %bb.1:
+; CHECK-NEXT:    svc #42
+; CHECK-NEXT:  .LBB3_2: @ %entry
+; CHECK-NEXT:    .save {r7, lr}
+; CHECK-NEXT:    push {r7, lr}
+; CHECK-NEXT:    .pad #64
+; CHECK-NEXT:    sub sp, #64
+; CHECK-NEXT:    mov r0, sp
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    add sp, #64
+; CHECK-NEXT:    pop {r7, pc}
+; CHECK-NEXT:    .p2align 2
+; CHECK-NEXT:  @ %bb.3:
+; CHECK-NEXT:  .LCPI3_0:
+; CHECK-NEXT:    .long __stack_boundary
+entry:
+  %buf = alloca [64 x i8], align 1
+  call void @sink(ptr %buf)
+  ret void
+}
+
+; All four argument registers are live on entry, so both scratch registers
+; (r2, r3) are preserved with push/pop around the check.
+define i32 @args_live(i32 %a, i32 %b, i32 %c, i32 %d) #0 {
+; CHECK-LABEL: args_live:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    push {r2, r3}
+; CHECK-NEXT:    mov r3, sp
+; CHECK-NEXT:    ldr r2, .LCPI4_0
+; CHECK-NEXT:    ldr r2, [r2]
+; CHECK-NEXT:    sub r3, r3, r2
+; CHECK-NEXT:    cmp r3, #88
+; CHECK-NEXT:    pop {r2, r3}
+; CHECK-NEXT:    bhs .LBB4_2
+; CHECK-NEXT:  @ %bb.1:
+; CHECK-NEXT:    svc #255
+; CHECK-NEXT:  .LBB4_2: @ %entry
+; CHECK-NEXT:    .save {r4, r5, r6, r7, lr}
+; CHECK-NEXT:    push {r4, r5, r6, r7, lr}
+; CHECK-NEXT:    .pad #68
+; CHECK-NEXT:    sub sp, #68
+; CHECK-NEXT:    mov r4, r3
+; CHECK-NEXT:    mov r5, r2
+; CHECK-NEXT:    mov r6, r1
+; CHECK-NEXT:    mov r7, r0
+; CHECK-NEXT:    add r0, sp, #4
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    adds r0, r5, r4
+; CHECK-NEXT:    adds r1, r7, r6
+; CHECK-NEXT:    adds r0, r1, r0
+; CHECK-NEXT:    add sp, #68
+; CHECK-NEXT:    pop {r4, r5, r6, r7, pc}
+; CHECK-NEXT:    .p2align 2
+; CHECK-NEXT:  @ %bb.3:
+; CHECK-NEXT:  .LCPI4_0:
+; CHECK-NEXT:    .long __stack_boundary
+entry:
+  %buf = alloca [64 x i8], align 1
+  call void @sink(ptr %buf)
+  %t0 = add i32 %a, %b
+  %t1 = add i32 %c, %d
+  %r = add i32 %t0, %t1
+  ret i32 %r
+}
+
+; Three argument registers are live (r0, r1, r2); only r2 collides with the
+; scratch registers, so just r2 is preserved and r3 is used directly.
+define i32 @three_args(i32 %a, i32 %b, i32 %c) #0 {
+; CHECK-LABEL: three_args:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    push {r2}
+; CHECK-NEXT:    mov r3, sp
+; CHECK-NEXT:    ldr r2, .LCPI5_0
+; CHECK-NEXT:    ldr r2, [r2]
+; CHECK-NEXT:    sub r3, r3, r2
+; CHECK-NEXT:    cmp r3, #80
+; CHECK-NEXT:    pop {r2}
+; CHECK-NEXT:    bhs .LBB5_2
+; CHECK-NEXT:  @ %bb.1:
+; CHECK-NEXT:    svc #255
+; CHECK-NEXT:  .LBB5_2: @ %entry
+; CHECK-NEXT:    .save {r4, r5, r6, lr}
+; CHECK-NEXT:    push {r4, r5, r6, lr}
+; CHECK-NEXT:    .pad #64
+; CHECK-NEXT:    sub sp, #64
+; CHECK-NEXT:    mov r4, r2
+; CHECK-NEXT:    mov r5, r1
+; CHECK-NEXT:    mov r6, r0
+; CHECK-NEXT:    mov r0, sp
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    adds r0, r6, r5
+; CHECK-NEXT:    adds r0, r0, r4
+; CHECK-NEXT:    add sp, #64
+; CHECK-NEXT:    pop {r4, r5, r6, pc}
+; CHECK-NEXT:    .p2align 2
+; CHECK-NEXT:  @ %bb.3:
+; CHECK-NEXT:  .LCPI5_0:
+; CHECK-NEXT:    .long __stack_boundary
+entry:
+  %buf = alloca [64 x i8], align 1
+  call void @sink(ptr %buf)
+  %t0 = add i32 %a, %b
+  %r = add i32 %t0, %c
+  ret i32 %r
+}
+
+; No attribute: no check is emitted.
+define void @no_attr() {
+; CHECK-LABEL: no_attr:
+; CHECK:       @ %bb.0: @ %entry
+; CHECK-NEXT:    .save {r7, lr}
+; CHECK-NEXT:    push {r7, lr}
+; CHECK-NEXT:    .pad #64
+; CHECK-NEXT:    sub sp, #64
+; CHECK-NEXT:    mov r0, sp
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    add sp, #64
+; CHECK-NEXT:    pop {r7, pc}
+entry:
+  %buf = alloca [64 x i8], align 1
+  call void @sink(ptr %buf)
+  ret void
+}
+
+; The attribute is present but there is no frame of its own: check skipped.
+define void @frameless() #0 {
+; CHECK-LABEL: frameless:
+; CHECK:       @ %bb.0: @ %entry
+; CHECK-NEXT:    bx lr
+entry:
+  ret void
+}
+
+attributes #0 = { "stack-limit-variable"="__stack_boundary" }
+attributes #1 = { "stack-limit-variable"="my_limit" }
+attributes #2 = { "stack-limit-variable"="__stack_boundary" 
"stack-limit-trap-number"="42" }
diff --git a/llvm/test/CodeGen/ARM/stack-limit-variable-thumb2.ll 
b/llvm/test/CodeGen/ARM/stack-limit-variable-thumb2.ll
new file mode 100644
index 0000000000000..61915e32e3a5b
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/stack-limit-variable-thumb2.ll
@@ -0,0 +1,148 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py 
UTC_ARGS: --version 6
+; RUN: llc -mtriple=thumbv7m-none-eabi -verify-machineinstrs < %s | FileCheck 
%s
+; RUN: llc -mtriple=thumbv7em-none-eabi -verify-machineinstrs < %s | FileCheck 
%s
+
+; The -fstack-limit-variable=<var> prologue is emitted only for functions that
+; carry the "stack-limit-variable"="<var>" attribute and have a non-empty stack
+; frame. It loads <var>, computes the free space (sp - limit), compares it
+; against the frame size and traps with "svc #255" when it is too small.
+; The Thumb2 sequence is identical on every Thumb2 (Cortex-M3 and later)
+; target, so both triples share the same checks.
+
+declare void @sink(ptr)
+
+; A function with the attribute and a real frame gets the full check sequence.
+define void @needs_check() #0 {
+; CHECK-LABEL: needs_check:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    movw r12, :lower16:__stack_boundary
+; CHECK-NEXT:    movt r12, :upper16:__stack_boundary
+; CHECK-NEXT:    ldr.w r12, [r12]
+; CHECK-NEXT:    sub.w r12, sp, r12
+; CHECK-NEXT:    cmp.w r12, #72
+; CHECK-NEXT:    it lo
+; CHECK-NEXT:    svclo #255
+; CHECK-NEXT:    .save {r7, lr}
+; CHECK-NEXT:    push {r7, lr}
+; CHECK-NEXT:    .pad #64
+; CHECK-NEXT:    sub sp, #64
+; CHECK-NEXT:    mov r0, sp
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    add sp, #64
+; CHECK-NEXT:    pop {r7, pc}
+entry:
+  %buf = alloca [64 x i8], align 1
+  call void @sink(ptr %buf)
+  ret void
+}
+
+; The variable name comes from the attribute, so a different value materializes
+; a different symbol.
+define void @custom_var() #1 {
+; CHECK-LABEL: custom_var:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    movw r12, :lower16:my_limit
+; CHECK-NEXT:    movt r12, :upper16:my_limit
+; CHECK-NEXT:    ldr.w r12, [r12]
+; CHECK-NEXT:    sub.w r12, sp, r12
+; CHECK-NEXT:    cmp.w r12, #72
+; CHECK-NEXT:    it lo
+; CHECK-NEXT:    svclo #255
+; CHECK-NEXT:    .save {r7, lr}
+; CHECK-NEXT:    push {r7, lr}
+; CHECK-NEXT:    .pad #64
+; CHECK-NEXT:    sub sp, #64
+; CHECK-NEXT:    mov r0, sp
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    add sp, #64
+; CHECK-NEXT:    pop {r7, pc}
+entry:
+  %buf = alloca [64 x i8], align 1
+  call void @sink(ptr %buf)
+  ret void
+}
+
+; A frame whose size is not directly encodable as a Thumb2 modified immediate 
is
+; rounded up to the next such value: the size is kept to its top 8 significant
+; bits (less than 1/256 overshoot).
+define void @large_frame() #0 {
+; CHECK-LABEL: large_frame:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    movw r12, :lower16:__stack_boundary
+; CHECK-NEXT:    movt r12, :upper16:__stack_boundary
+; CHECK-NEXT:    ldr.w r12, [r12]
+; CHECK-NEXT:    sub.w r12, sp, r12
+; CHECK-NEXT:    cmp.w r12, #4016
+; CHECK-NEXT:    it lo
+; CHECK-NEXT:    svclo #255
+; CHECK-NEXT:    .save {r7, lr}
+; CHECK-NEXT:    push {r7, lr}
+; CHECK-NEXT:    .pad #4000
+; CHECK-NEXT:    sub.w sp, sp, #4000
+; CHECK-NEXT:    mov r0, sp
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    add.w sp, sp, #4000
+; CHECK-NEXT:    pop {r7, pc}
+entry:
+  %buf = alloca [4000 x i8], align 1
+  call void @sink(ptr %buf)
+  ret void
+}
+
+; Same body, but without the attribute: no check is emitted.
+define void @no_attr() {
+; CHECK-LABEL: no_attr:
+; CHECK:       @ %bb.0: @ %entry
+; CHECK-NEXT:    .save {r7, lr}
+; CHECK-NEXT:    push {r7, lr}
+; CHECK-NEXT:    .pad #64
+; CHECK-NEXT:    sub sp, #64
+; CHECK-NEXT:    mov r0, sp
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    add sp, #64
+; CHECK-NEXT:    pop {r7, pc}
+entry:
+  %buf = alloca [64 x i8], align 1
+  call void @sink(ptr %buf)
+  ret void
+}
+
+; The attribute is present but the function has no frame of its own, so the
+; check is skipped.
+define void @frameless() #0 {
+; CHECK-LABEL: frameless:
+; CHECK:       @ %bb.0: @ %entry
+; CHECK-NEXT:    bx lr
+entry:
+  ret void
+}
+
+; The trap immediate is taken from "stack-limit-trap-number", so this function
+; traps with "svc #42" instead of the default "svc #255".
+define void @custom_trap() #2 {
+; CHECK-LABEL: custom_trap:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    movw r12, :lower16:__stack_boundary
+; CHECK-NEXT:    movt r12, :upper16:__stack_boundary
+; CHECK-NEXT:    ldr.w r12, [r12]
+; CHECK-NEXT:    sub.w r12, sp, r12
+; CHECK-NEXT:    cmp.w r12, #72
+; CHECK-NEXT:    it lo
+; CHECK-NEXT:    svclo #42
+; CHECK-NEXT:    .save {r7, lr}
+; CHECK-NEXT:    push {r7, lr}
+; CHECK-NEXT:    .pad #64
+; CHECK-NEXT:    sub sp, #64
+; CHECK-NEXT:    mov r0, sp
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    add sp, #64
+; CHECK-NEXT:    pop {r7, pc}
+entry:
+  %buf = alloca [64 x i8], align 1
+  call void @sink(ptr %buf)
+  ret void
+}
+
+attributes #0 = { "stack-limit-variable"="__stack_boundary" }
+attributes #1 = { "stack-limit-variable"="my_limit" }
+attributes #2 = { "stack-limit-variable"="__stack_boundary" 
"stack-limit-trap-number"="42" }

>From a3b014a7e938a2a3a834b214b6f60058545b9484 Mon Sep 17 00:00:00 2001
From: Zhiyao Ma <[email protected]>
Date: Wed, 8 Jul 2026 00:59:01 -0400
Subject: [PATCH 2/2] Address review feedback for -fstack-limit-variable

- Flip the comparison on both Thumb variants. Form the lowest
  admissible SP value (limit + frame size) and compare SP against it
  directly, instead of computing the free space (SP - limit), which
  wrapped and wrongly passed the check when SP was already at or below
  the limit. This also saves one instruction on Thumb1 by not copying
  SP into a scratch register.

- Thumb1: preserve the first live scratch register by stashing it in
  r12 with a pair of "mov"s instead of push/pop, touching no memory.
  Small frames need only this one register.

- Thumb1: with a large frame requiring two scratch registers for the
  check, the second one still uses push/pop. The check is split in two
  so SP is compared against the limit itself before the push, which then
  provably never writes below the limit. The guard bytes previously
  reserved below the limit are no longer needed.

- Reject functions where r12 is live on entry (a 'nest' parameter)
  with a fatal error instead of pessimizing the sequence to preserve
  it.

- State in the help text and doc brief that the limit value must be
  4-byte aligned.
---
 clang/include/clang/Options/Options.td        |   6 +-
 llvm/lib/Target/ARM/ARMFrameLowering.cpp      | 417 ++++++++++++------
 .../ARM/stack-limit-variable-diagnostics.ll   |  21 +-
 .../ARM/stack-limit-variable-thumb1.ll        | 209 +++++++--
 .../ARM/stack-limit-variable-thumb2.ll        |  24 +-
 5 files changed, 485 insertions(+), 192 deletions(-)

diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index be8b0f4622a31..da558e12ffa02 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4470,11 +4470,13 @@ def fstack_limit_variable_EQ : Joined<["-"], 
"fstack-limit-variable=">, Group<f_
   Visibility<[ClangOption, CC1Option]>,
   HelpText<"Emit a function prologue that traps when the free stack space "
            "(SP - <var>) is smaller than the frame this function needs. "
-           "<var> is a global variable holding the stack limit">,
+           "<var> is a global variable holding the stack limit. The limit "
+           "value must be 4-byte aligned">,
   MarshallingInfoString<CodeGenOpts<"StackLimitVariable">>,
   DocBrief<"Emit a function prologue that compares free stack space against 
the "
            "global named by <var> and executes a trap instruction on "
-           "insufficient space">;
+           "insufficient space. The value stored in <var> must be 4-byte "
+           "aligned">;
 def fstack_limit_trap_number_EQ : Joined<["-"], "fstack-limit-trap-number=">, 
Group<f_Group>,
   Visibility<[ClangOption, CC1Option]>,
   HelpText<"Set the immediate operand of the trap instruction emitted by "
diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp 
b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index 2c1ffac074348..00ce471c9b909 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -3737,9 +3737,10 @@ void ARMFrameLowering::adjustForSegmentedStacks(
 }
 
 /// Return the smallest value >= \p StackSize that is encodable as a Thumb2
-/// "modified immediate" so it can be used directly as the operand of a CMP.
-/// Rounding the required size up is conservative. It can only make the check
-/// trap slightly earlier, never miss a genuine overflow.
+/// "modified immediate" so it can be used directly as the operand of the ADD
+/// that forms the lowest admissible SP value. Rounding the required size up is
+/// conservative. It can only make the check trap slightly earlier, never miss
+/// a genuine overflow.
 static unsigned getEncodableRequiredSize(uint64_t StackSize) {
   unsigned V = StackSize > 0xFFFFFFFFULL ? 0xFFFFFFFFU
                                          : static_cast<unsigned>(StackSize);
@@ -3773,8 +3774,12 @@ static unsigned getEncodableRequiredSize(uint64_t 
StackSize) {
 //
 //   1. Reads the global variable named by the attribute (the lowest address
 //      the stack is allowed to reach).
-//   2. Computes the currently free stack space as (SP - <var>).
-//   3. Compares that against the stack size required by this function's frame.
+//   2. Computes the lowest admissible SP value as <var> plus the stack size
+//      required by this function's frame.
+//   3. Compares SP against it. The sum can only wrap around zero if the limit
+//      lies within a frame size of the top of the address space, which cannot
+//      happen for a downward-growing stack, and an SP already at or below the
+//      limit always traps.
 //   4. If there is enough space the function body runs as usual; otherwise it
 //      executes "svc #<n>", where <n> is the trap immediate selected by
 //      -fstack-limit-trap-number (default 255).
@@ -3786,55 +3791,36 @@ static unsigned getEncodableRequiredSize(uint64_t 
StackSize) {
 //
 //     movw r12, :lower16:__stack_boundary
 //     movt r12, :upper16:__stack_boundary
-//     ldr  r12, [r12]          ; r12 = __stack_boundary (limit)
-//     sub  r12, sp, r12        ; r12 = free space = SP - limit
-//     cmp  r12, #frameSize
-//     bhs  .Lbody              ; enough space -> run the body
-//     svc  #<n>                ; insufficient space -> trap
+//     ldr  r12, [r12]           ; r12 = __stack_boundary (limit)
+//     add  r12, r12, #frameSize ; r12 = lowest admissible SP
+//     cmp  sp, r12
+//     bhs  .Lbody               ; enough space -> run the body
+//     svc  #<n>                 ; insufficient space -> trap
 //   .Lbody:
 //     <original prologue/body>
 //
-// r12 (IP) is a call-clobbered scratch register that is never live on entry
-// under AAPCS, so the Thumb2 sequence needs no save/restore.
+// Both variants treat r12 (IP) as a free scratch register. Thumb2 computes the
+// check in it and Thumb1 uses it as a spill-free save slot. Under AAPCS r12 is
+// call-clobbered and carries no value into a function, with one exception: a
+// 'nest' parameter is passed in r12. Rather than pessimizing the sequence to
+// preserve it, the pass rejects functions where r12 is live on entry with a
+// fatal error, which is extremely unlikely.
 //
 // Thumb1 (thumbv6m / Cortex-M0) has neither movw/movt nor the wide encodings,
-// so the Thumb1 sequence loads the limit's address from the constant pool and
-// does the arithmetic in two low registers. It uses r2 and r3: under AAPCS the
-// integer argument registers are assigned r0, r1, r2, r3 in order, so r2/r3 
are
-// the argument registers least likely to hold an incoming value. Whichever of
-// them is dead on entry is used directly; whichever still carries a live
-// argument is preserved with push/pop around the check (r12 cannot be used
-// instead because Thumb1 low-register instructions cannot address it):
+// so the same check is built from 16-bit instructions. The limit's address is
+// loaded from the constant pool and the sum is formed in a low register.
 //
-//     push {r2, r3}?             ; only the scratch regs that are live on 
entry
-//     mov  r3, sp
-//     ldr  r2, =__stack_boundary ; via a PC-relative constant pool entry
-//     ldr  r2, [r2]              ; r2 = __stack_boundary (limit)
-//     subs r3, r3, r2            ; r3 = free space = SP - limit
-//     cmp  r3, #frameSize        ; (large frames: cmp against r2)
-//     pop  {r2, r3}?
+//     mov  r12, r3?              ; stash r3 in IP if it is live on entry
+//     ldr  r3, =__stack_boundary ; via a PC-relative constant pool entry
+//     ldr  r3, [r3]              ; r3 = __stack_boundary (limit)
+//     adds r3, #frameSize        ; r3 = lowest admissible SP
+//                                ; (large frames: ldr r2, =size; adds 
r3,r3,r2)
+//     cmp  sp, r3
+//     mov  r3, r12?              ; restore; high-register "mov" keeps the 
flags
 //     bhs  .Lbody
 //     svc  #<n>
 //   .Lbody:
 //     <original prologue/body>
-//
-// The push is a transient write *below* SP: preserving one or two registers
-// stores 4 or 8 bytes into [SP-8, SP) before the comparison runs. If SP has
-// already reached (or is within 8 bytes of) the limit, that store lands below
-// __stack_boundary. The pass therefore requires the 8 bytes immediately below
-// __stack_boundary to be safe to write: whoever defines the limit must leave 
at
-// least 8 bytes of reserved (guard) space between __stack_boundary and the
-// true, unusable end of the stack. Those bytes are only touched transiently,
-// and they are popped again before the trap, so an 8-byte reservation is 
enough
-// no matter how many registers are preserved. (The Thumb2 path uses r12 and
-// writes no memory, so it needs no such reservation.)
-//
-// Because that same spill lowers SP before SP is read, the Thumb1 free-space
-// estimate is also low by 4 bytes per preserved register (0, 4 or 8). The 
check
-// stays conservative and can only trap slightly early, never miss a real
-// overflow. Either way the only registers disturbed on the fall-through path
-// are the dead scratch registers and the condition flags, so all live argument
-// and callee-saved registers reach the original prologue untouched.
 void ARMFrameLowering::adjustForStackLimitCheck(MachineFunction &MF) const {
   const Function &F = MF.getFunction();
   const ARMSubtarget &ST = MF.getSubtarget<ARMSubtarget>();
@@ -3889,37 +3875,62 @@ void 
ARMFrameLowering::adjustForStackLimitCheck(MachineFunction &MF) const {
   if (StackSize == 0)
     return;
 
+  // Both sequences use r12 (IP) as a scratch register. Thumb2 computes the
+  // check in it, and Thumb1 stashes a live low scratch register in it. Under
+  // AAPCS r12 carries no value into a function, with a single exception: a
+  // 'nest' parameter (a static chain pointer) is passed in r12. Rather than
+  // pessimizing the sequence to preserve r12, reject the function. In practice
+  // this never fires for the main embedded languages. Clang never emits a
+  // 'nest' parameter on a function it compiles from C/C++ (GCC-style nested
+  // functions are not supported. The static chain only arises at call sites
+  // via __builtin_call_with_static_chain), and rustc never uses 'nest' either
+  // (closures capture through an ordinary environment parameter). Static
+  // chains come from other frontends, e.g. Fortran or Ada inner procedures, or
+  // from hand-written IR.
+  if (!MF.getProperties().hasTracksLiveness() ||
+      MF.front().isLiveIn(ARM::R12)) {
+    report_fatal_error("-fstack-limit-variable uses r12 (IP) as a scratch "
+                       "register, but r12 cannot be proven free on entry to "
+                       "function '" +
+                       Twine(F.getName()) +
+                       "'. Functions taking a 'nest' parameter, which is "
+                       "passed in r12, are not supported");
+  }
+
   const TargetInstrInfo &TII = *ST.getInstrInfo();
 
-  MachineBasicBlock *OrigEntry = &MF.front();
+  // BodyMBB is the original entry block, i.e. the ".Lbody" label of the
+  // sketches above, the original prologue and function body.
+  MachineBasicBlock *BodyMBB = &MF.front();
   const DebugLoc DL; // Prologue instructions carry no source location.
 
   // CheckMBB:  materialize/compare and branch over the trap.
   // TrapMBB:   svc #<n> on insufficient stack.
-  // Layout:    CheckMBB -> TrapMBB -> OrigEntry, with CheckMBB conditionally
-  //            branching straight to OrigEntry when there is enough space.
+  // Layout:    CheckMBB -> TrapMBB -> BodyMBB, with CheckMBB conditionally
+  //            branching straight to BodyMBB when there is enough space.
+  //            (When the Thumb1 sequence must push a scratch register it
+  //            splits the check across two more blocks, see below.)
   MachineBasicBlock *CheckMBB = MF.CreateMachineBasicBlock();
   MachineBasicBlock *TrapMBB = MF.CreateMachineBasicBlock();
 
-  MachineFunction::iterator EntryIt = OrigEntry->getIterator();
-  MF.insert(EntryIt, CheckMBB);
-  MF.insert(EntryIt, TrapMBB);
+  MachineFunction::iterator BodyIt = BodyMBB->getIterator();
+  MF.insert(BodyIt, CheckMBB);
+  MF.insert(BodyIt, TrapMBB);
 
   // The function's incoming live registers now enter through CheckMBB and flow
-  // unchanged into the original entry block.
-  for (const auto &LI : OrigEntry->liveins()) {
+  // unchanged into the body.
+  for (const auto &LI : BodyMBB->liveins()) {
     CheckMBB->addLiveIn(LI.PhysReg, LI.LaneMask);
     TrapMBB->addLiveIn(LI.PhysReg, LI.LaneMask);
   }
 
-  CheckMBB->addSuccessor(OrigEntry);
-  CheckMBB->addSuccessor(TrapMBB);
-  TrapMBB->addSuccessor(OrigEntry);
+  TrapMBB->addSuccessor(BodyMBB);
 
   if (ST.isThumb2()) {
     // ---- Thumb2 (thumbv7m / Cortex-M3, thumbv7em / Cortex-M4 and later) ----
-    // The free space is computed in IP (r12), a call-clobbered scratch 
register
-    // that is never live on entry under AAPCS, so nothing needs to be saved.
+    // The whole check lives in CheckMBB. It is computed in IP (r12), a
+    // call-clobbered scratch register that was proven free on entry above,
+    // so nothing needs saving.
     const Register Scratch = ARM::R12;
     const unsigned RequiredSize = getEncodableRequiredSize(StackSize);
 
@@ -3939,109 +3950,253 @@ void 
ARMFrameLowering::adjustForStackLimitCheck(MachineFunction &MF) const {
         .addReg(Scratch)
         .addImm(0)
         .add(predOps(ARMCC::AL));
-    // sub r12, sp, r12 ; r12 = SP - limit = free stack space
-    BuildMI(CheckMBB, DL, TII.get(ARM::t2SUBrr), Scratch)
-        .addReg(ARM::SP)
+    // add r12, r12, #frameSize ; r12 = lowest admissible SP
+    BuildMI(CheckMBB, DL, TII.get(ARM::t2ADDri), Scratch)
         .addReg(Scratch)
+        .addImm(RequiredSize)
         .add(predOps(ARMCC::AL))
         .add(condCodeOp());
-    // cmp r12, #frameSize
-    BuildMI(CheckMBB, DL, TII.get(ARM::t2CMPri))
+    // cmp sp, r12
+    BuildMI(CheckMBB, DL, TII.get(ARM::t2CMPrr))
+        .addReg(ARM::SP)
         .addReg(Scratch)
-        .addImm(RequiredSize)
         .add(predOps(ARMCC::AL));
+    // bhs BodyMBB      ; enough stack space (unsigned) -> run the body
+    CheckMBB->addSuccessor(BodyMBB);
+    CheckMBB->addSuccessor(TrapMBB);
+    BuildMI(CheckMBB, DL, TII.get(ARM::tBcc))
+        .addMBB(BodyMBB)
+        .addImm(ARMCC::HS)
+        .addReg(ARM::CPSR);
   } else {
     // ---- Thumb1 (thumbv6m / Cortex-M0) ----
     // ARMv6-M has no movw/movt or wide encodings, so the limit's address is
-    // loaded from the constant pool and the arithmetic uses two low registers.
-    // We use r2 and r3. Under AAPCS the integer argument registers are 
assigned
-    // r0, r1, r2, r3 in order, so r2/r3 are the argument registers least 
likely
-    // to carry an incoming value. Whichever of them is dead on entry is
-    // clobbered directly. Whichever still holds a live argument is preserved
-    // with push/pop around the check. (r2/r3 are low registers, so the Thumb1
-    // low-register instructions can address them. r12 cannot be used here.)
-    const Register S0 = ARM::R2; // &limit -> limit -> (required size)
-    const Register S1 = ARM::R3; // sp -> free space
+    // loaded from the constant pool and the lowest admissible SP value is
+    // formed in a low register.
     const unsigned RawSize = StackSize > 0xFFFFFFFFULL
                                  ? 0xFFFFFFFFU
                                  : static_cast<unsigned>(StackSize);
+    // Small frames fit the 8-bit "adds" immediate and need one scratch
+    // register. Larger sizes are loaded from the constant pool and need a
+    // second one. The scratches are r3 and r2 (in that order): under AAPCS the
+    // integer argument registers are assigned r0, r1, r2, r3 in order, so
+    // these are the argument registers least likely to hold an incoming value.
+    // They must be low registers because the Thumb1 loads and adds cannot
+    // address r12.
+    const bool LargeFrame = RawSize > 255;
+    const Register AccReg = ARM::R3;  // &limit -> limit -> limit + frameSize
+    const Register SizeReg = ARM::R2; // frameSize (large frames only)
+
+    // A scratch register that is dead on entry is clobbered directly. Live
+    // ones must be preserved. The first is stashed in r12 (proven free above)
+    // with a pair of "mov"s, which touches no memory and leaves SP unchanged.
+    // r12 is a single slot, so when a large-frame check needs both r2 and r3,
+    // the second one uses a push/pop pair. After the restores the only
+    // registers disturbed on the fall-through path are the dead scratch
+    // registers, r12 and the condition flags, so all live argument and
+    // callee-saved registers reach the original prologue untouched.
+    Register StashReg;
+    Register PushReg;
+    SmallVector<Register, 2> UsedRegs = {AccReg};
+    if (LargeFrame)
+      UsedRegs.push_back(SizeReg);
+    for (Register R : UsedRegs) {
+      if (!CheckMBB->isLiveIn(R))
+        continue;
+      if (!StashReg)
+        StashReg = R;
+      else
+        PushReg = R;
+    }
 
-    // Preserve whichever scratch registers are still carrying an incoming
-    // argument (i.e. are live on entry). If liveness is not being tracked we
-    // cannot prove they are dead, so conservatively preserve both.
-    const bool TracksLiveness = MF.getProperties().hasTracksLiveness();
-    SmallVector<Register, 2> SaveRegs;
-    for (Register R : {S0, S1})
-      if (!TracksLiveness || CheckMBB->isLiveIn(R))
-        SaveRegs.push_back(R);
-
-    // push {<live scratch regs>}  ; only those holding a live argument. This
-    // stores up to 8 bytes into [SP-8, SP) before the limit is checked, so the
-    // 8 bytes just below the limit must be reserved/safe to write (see the
-    // comment above). The spill also lowers SP before it is read below, so the
-    // free-space estimate is low by 4 bytes per preserved register (0, 4 or 
8).
-    // The check stays conservative and can never miss a real overflow.
-    if (!SaveRegs.empty()) {
-      MachineInstrBuilder Push =
-          BuildMI(CheckMBB, DL, TII.get(ARM::tPUSH)).add(predOps(ARMCC::AL));
-      for (Register R : SaveRegs)
-        Push.addReg(R);
-    }
-    // mov r3, sp
-    BuildMI(CheckMBB, DL, TII.get(ARM::tMOVr), S1)
-        .addReg(ARM::SP)
-        .add(predOps(ARMCC::AL));
-    // ldr r2, =<var>   ; r2 = &<var> via a PC-relative constant pool entry
+    // mov r12, <reg>   ; stash the first live scratch register in r12
+    if (StashReg) {
+      BuildMI(CheckMBB, DL, TII.get(ARM::tMOVr), ARM::R12)
+          .addReg(StashReg)
+          .add(predOps(ARMCC::AL));
+    }
+    // ldr r3, =<var>   ; r3 = &<var> via a PC-relative constant pool entry
     ARMFunctionInfo *AFI = MF.getInfo<ARMFunctionInfo>();
     unsigned PCLabelId = AFI->createPICLabelUId();
     ARMConstantPoolValue *CPV =
         ARMConstantPoolSymbol::Create(F.getContext(), LimitVar, PCLabelId, 0);
     unsigned CPI = MF.getConstantPool()->getConstantPoolIndex(CPV, Align(4));
-    BuildMI(CheckMBB, DL, TII.get(ARM::tLDRpci), S0)
+    BuildMI(CheckMBB, DL, TII.get(ARM::tLDRpci), AccReg)
         .addConstantPoolIndex(CPI)
         .add(predOps(ARMCC::AL));
-    // ldr r2, [r2]     ; r2 = value of <var> (the stack limit)
-    BuildMI(CheckMBB, DL, TII.get(ARM::tLDRi), S0)
-        .addReg(S0)
+    // ldr r3, [r3]     ; r3 = value of <var> (the stack limit)
+    BuildMI(CheckMBB, DL, TII.get(ARM::tLDRi), AccReg)
+        .addReg(AccReg)
         .addImm(0)
         .add(predOps(ARMCC::AL));
-    // subs r3, r3, r2  ; r3 = SP - limit = free stack space
-    BuildMI(CheckMBB, DL, TII.get(ARM::tSUBrr), S1)
-        .add(condCodeOp())
-        .addReg(S1)
-        .addReg(S0)
-        .add(predOps(ARMCC::AL));
-    // cmp r3, #frameSize for small frames; for larger ones load the required
-    // size into r2 from the constant pool and compare registers.
-    if (RawSize <= 255) {
-      BuildMI(CheckMBB, DL, TII.get(ARM::tCMPi8))
-          .addReg(S1)
+
+    if (!LargeFrame) {
+      // ---- Small frames ----
+      // The size fits the 8-bit "adds" immediate, only r3 is needed, and the
+      // whole check lives in CheckMBB.
+
+      // adds r3, #frameSize ; r3 = lowest admissible SP
+      BuildMI(CheckMBB, DL, TII.get(ARM::tADDi8), AccReg)
+          .add(t1CondCodeOp())
+          .addReg(AccReg)
           .addImm(RawSize)
           .add(predOps(ARMCC::AL));
-    } else {
+      // cmp sp, r3
+      BuildMI(CheckMBB, DL, TII.get(ARM::tCMPhir))
+          .addReg(ARM::SP)
+          .addReg(AccReg)
+          .add(predOps(ARMCC::AL));
+      // mov r3, r12      ; restore r3 if it was stashed. A "mov" involving a
+      // high register leaves the flags intact.
+      if (StashReg) {
+        BuildMI(CheckMBB, DL, TII.get(ARM::tMOVr), StashReg)
+            .addReg(ARM::R12)
+            .add(predOps(ARMCC::AL));
+      }
+      // bhs BodyMBB      ; enough stack space (unsigned) -> run the body
+      CheckMBB->addSuccessor(BodyMBB);
+      CheckMBB->addSuccessor(TrapMBB);
+      BuildMI(CheckMBB, DL, TII.get(ARM::tBcc))
+          .addMBB(BodyMBB)
+          .addImm(ARMCC::HS)
+          .addReg(ARM::CPSR);
+    } else if (!PushReg) {
+      // ---- Large frames, at most one live scratch register ----
+      // The size does not fit the "adds" immediate, so it is loaded into r2
+      // from the constant pool and the registers are added. The whole check
+      // still lives in CheckMBB.
+
+      // ldr r2, =<size>  ; then adds r3, r3, r2: r3 = lowest admissible SP
       MachineBasicBlock::iterator It = CheckMBB->end();
-      ST.getRegisterInfo()->emitLoadConstPool(*CheckMBB, It, DL, S0, 0,
+      ST.getRegisterInfo()->emitLoadConstPool(*CheckMBB, It, DL, SizeReg, 0,
                                               RawSize);
-      BuildMI(CheckMBB, DL, TII.get(ARM::tCMPr))
-          .addReg(S1)
-          .addReg(S0)
+      BuildMI(CheckMBB, DL, TII.get(ARM::tADDrr), AccReg)
+          .add(t1CondCodeOp())
+          .addReg(AccReg)
+          .addReg(SizeReg)
           .add(predOps(ARMCC::AL));
-    }
-    // pop {<live scratch regs>}   ; restore (does not affect the flags)
-    if (!SaveRegs.empty()) {
-      MachineInstrBuilder Pop =
-          BuildMI(CheckMBB, DL, TII.get(ARM::tPOP)).add(predOps(ARMCC::AL));
-      for (Register R : SaveRegs)
-        Pop.addReg(R);
+      // cmp sp, r3
+      BuildMI(CheckMBB, DL, TII.get(ARM::tCMPhir))
+          .addReg(ARM::SP)
+          .addReg(AccReg)
+          .add(predOps(ARMCC::AL));
+      // mov <reg>, r12   ; restore the stashed scratch register, if any. A
+      // "mov" involving a high register leaves the flags intact.
+      if (StashReg) {
+        BuildMI(CheckMBB, DL, TII.get(ARM::tMOVr), StashReg)
+            .addReg(ARM::R12)
+            .add(predOps(ARMCC::AL));
+      }
+      // bhs BodyMBB      ; enough stack space (unsigned) -> run the body
+      CheckMBB->addSuccessor(BodyMBB);
+      CheckMBB->addSuccessor(TrapMBB);
+      BuildMI(CheckMBB, DL, TII.get(ARM::tBcc))
+          .addMBB(BodyMBB)
+          .addImm(ARMCC::HS)
+          .addReg(ARM::CPSR);
+    } else {
+      // ---- Large frames with both scratch registers live ----
+      // r3 is stashed in r12, but that is a single slot, so r2 must be
+      // preserved with a push/pop pair. The push would write below SP before
+      // anything has been checked, and if SP were already at or below the
+      // limit that write would land below the limit. Split the check instead:
+      // compare SP against the limit itself first and trap if the stack is
+      // exhausted. SP is architecturally kept 4-byte aligned on ARMv6-M, so
+      // provided the limit value is 4-byte aligned too, surviving this first
+      // check proves SP >= limit + 4 and the push in the second block stays
+      // at or above the limit. The check is laid out across three blocks:
+      //
+      //   CheckMBB:     exhaustion check, "cmp sp, r3" against the limit,
+      //                 branching to RestoreMBB on LS (SP <= limit).
+      //   SizeCheckMBB: frame-size check, "push {r2}" and "cmp sp, r3"
+      //                 against limit + (alignedSize - 8), falling through.
+      //   RestoreMBB:   runs "mov r3, r12" for both paths and branches to
+      //                 BodyMBB or falls into TrapMBB on the flags of
+      //                 whichever comparison ran last (the high-register "mov"
+      //                 and the pop leave them intact).
+      //
+      // For one branch condition to fit both comparisons, the final branch
+      // is HI, the exact complement of LS. The frame-size check compensates
+      // with a bias. The frame size is rounded up to a multiple of 4 and
+      // lowered by 8. SP, the limit (required 4-byte aligned, see above)
+      // and alignedSize are then all multiples of 4, so checking
+      // SP - 4 > limit + alignedSize - 8 is equivalent to
+      // SP >= limit + alignedSize.
+      //
+      // See large_frame_args_live in
+      // llvm/test/CodeGen/ARM/stack-limit-variable-thumb1.ll for the full
+      // generated sequence.
+      MachineBasicBlock *SizeCheckMBB = MF.CreateMachineBasicBlock();
+      MachineBasicBlock *RestoreMBB = MF.CreateMachineBasicBlock();
+      MF.insert(TrapMBB->getIterator(), SizeCheckMBB);
+      MF.insert(TrapMBB->getIterator(), RestoreMBB);
+
+      for (const auto &LI : CheckMBB->liveins()) {
+        SizeCheckMBB->addLiveIn(LI.PhysReg, LI.LaneMask);
+        RestoreMBB->addLiveIn(LI.PhysReg, LI.LaneMask);
+      }
+      SizeCheckMBB->addLiveIn(ARM::R12);
+      RestoreMBB->addLiveIn(ARM::R12);
+      // The final branch in RestoreMBB consumes flags set by its
+      // predecessors.
+      RestoreMBB->addLiveIn(ARM::CPSR);
+      CheckMBB->addSuccessor(SizeCheckMBB);
+      CheckMBB->addSuccessor(RestoreMBB);
+      SizeCheckMBB->addSuccessor(RestoreMBB);
+      RestoreMBB->addSuccessor(BodyMBB);
+      RestoreMBB->addSuccessor(TrapMBB);
+
+      // CheckMBB (continued): the exhaustion check.
+      // cmp sp, r3       ; stack already exhausted?
+      BuildMI(CheckMBB, DL, TII.get(ARM::tCMPhir))
+          .addReg(ARM::SP)
+          .addReg(AccReg)
+          .add(predOps(ARMCC::AL));
+      // bls RestoreMBB   ; SP <= limit (unsigned) -> restore r3 and trap
+      BuildMI(CheckMBB, DL, TII.get(ARM::tBcc))
+          .addMBB(RestoreMBB)
+          .addImm(ARMCC::LS)
+          .addReg(ARM::CPSR);
+
+      // SizeCheckMBB: the frame-size check.
+      // push {r2}        ; safe: the first check proved SP >= limit + 4
+      BuildMI(SizeCheckMBB, DL, TII.get(ARM::tPUSH))
+          .add(predOps(ARMCC::AL))
+          .addReg(PushReg);
+      // ldr r2, =<alignedSize - 8>
+      const unsigned BiasedSize = alignTo(RawSize, 4) - 8;
+      MachineBasicBlock::iterator It = SizeCheckMBB->end();
+      ST.getRegisterInfo()->emitLoadConstPool(*SizeCheckMBB, It, DL, SizeReg, 
0,
+                                              BiasedSize);
+      // adds r3, r3, r2
+      BuildMI(SizeCheckMBB, DL, TII.get(ARM::tADDrr), AccReg)
+          .add(t1CondCodeOp())
+          .addReg(AccReg)
+          .addReg(SizeReg)
+          .add(predOps(ARMCC::AL));
+      // cmp sp, r3
+      BuildMI(SizeCheckMBB, DL, TII.get(ARM::tCMPhir))
+          .addReg(ARM::SP)
+          .addReg(AccReg)
+          .add(predOps(ARMCC::AL));
+      // pop {r2}         ; a pop of low registers leaves the flags intact
+      BuildMI(SizeCheckMBB, DL, TII.get(ARM::tPOP))
+          .add(predOps(ARMCC::AL))
+          .addReg(PushReg);
+
+      // RestoreMBB: shared restore and final branch.
+      // mov r3, r12
+      BuildMI(RestoreMBB, DL, TII.get(ARM::tMOVr), StashReg)
+          .addReg(ARM::R12)
+          .add(predOps(ARMCC::AL));
+      // bhi BodyMBB      ; enough stack space (unsigned) -> run the body
+      BuildMI(RestoreMBB, DL, TII.get(ARM::tBcc))
+          .addMBB(BodyMBB)
+          .addImm(ARMCC::HI)
+          .addReg(ARM::CPSR);
     }
   }
 
-  // bhs OrigEntry    ; free >= required (unsigned) -> run the body
-  BuildMI(CheckMBB, DL, TII.get(ARM::tBcc))
-      .addMBB(OrigEntry)
-      .addImm(ARMCC::HS)
-      .addReg(ARM::CPSR);
-
   // svc #<n>         ; insufficient stack space
   BuildMI(TrapMBB, DL, TII.get(ARM::tSVC))
       .addImm(TrapNumber)
diff --git a/llvm/test/CodeGen/ARM/stack-limit-variable-diagnostics.ll 
b/llvm/test/CodeGen/ARM/stack-limit-variable-diagnostics.ll
index 2872b1da4740f..6e8659a33b744 100644
--- a/llvm/test/CodeGen/ARM/stack-limit-variable-diagnostics.ll
+++ b/llvm/test/CodeGen/ARM/stack-limit-variable-diagnostics.ll
@@ -1,6 +1,7 @@
 ; Because the -fstack-limit-variable stack check guards against stack overflow,
 ; a request the backend cannot honour is reported rather than silently dropped:
-; an unsupported target or a malformed attribute is a hard error.
+; an unsupported target, a malformed attribute or a function whose entry state
+; the check sequence would corrupt is a hard error.
 
 ; RUN: rm -rf %t && split-file %s %t
 ; RUN: not --crash llc -mtriple=armv7-none-eabi %t/a32.ll -o /dev/null 2>&1 | 
FileCheck %s --check-prefix=A32
@@ -8,10 +9,17 @@
 ; RUN: not --crash llc -mtriple=thumbv7m-none-eabi %t/empty.ll -o /dev/null 
2>&1 | FileCheck %s --check-prefix=EMPTY
 ; RUN: not --crash llc -mtriple=thumbv7m-none-eabi %t/badtrap.ll -o /dev/null 
2>&1 | FileCheck %s --check-prefix=BADTRAP
 
+; Both the Thumb2 and Thumb1 sequences use r12 (IP) as a scratch register, so
+; a function where r12 is live on entry (it carries the 'nest' parameter) is
+; rejected on both.
+; RUN: not --crash llc -mtriple=thumbv7m-none-eabi %t/nest.ll -o /dev/null 
2>&1 | FileCheck %s --check-prefix=NEST
+; RUN: not --crash llc -mtriple=thumbv6m-none-eabi %t/nest.ll -o /dev/null 
2>&1 | FileCheck %s --check-prefix=NEST
+
 ; A32: LLVM ERROR: {{.*}}-fstack-limit-variable is not supported for ARM (A32) 
code. Only Thumb (Cortex-M) targets are supported
 ; XO: LLVM ERROR: {{.*}}-fstack-limit-variable is not supported for 
execute-only ARMv6-M (Thumb1) code
 ; EMPTY: LLVM ERROR: {{.*}}'stack-limit-variable' attribute must name a 
non-empty global variable
 ; BADTRAP: LLVM ERROR: {{.*}}'stack-limit-trap-number' value '999' is not a 
valid supervisor-call number. Expected an integer in the range 0-255
+; NEST: LLVM ERROR: {{.*}}-fstack-limit-variable uses r12 (IP) as a scratch 
register, but r12 cannot be proven free on entry to function 'f'. Functions 
taking a 'nest' parameter, which is passed in r12, are not supported
 
 ;--- a32.ll
 define void @f() #0 { ret void }
@@ -28,3 +36,14 @@ attributes #0 = { "stack-limit-variable"="" }
 ;--- badtrap.ll
 define void @f() #0 { ret void }
 attributes #0 = { "stack-limit-variable"="__stack_boundary" 
"stack-limit-trap-number"="999" }
+
+;--- nest.ll
+declare void @sink(ptr)
+define void @f(ptr nest %chain) #0 {
+entry:
+  %buf = alloca [64 x i8], align 1
+  call void @sink(ptr %buf)
+  call void @sink(ptr %chain)
+  ret void
+}
+attributes #0 = { "stack-limit-variable"="__stack_boundary" }
diff --git a/llvm/test/CodeGen/ARM/stack-limit-variable-thumb1.ll 
b/llvm/test/CodeGen/ARM/stack-limit-variable-thumb1.ll
index ab256fbdad036..ca86e7db0b10a 100644
--- a/llvm/test/CodeGen/ARM/stack-limit-variable-thumb1.ll
+++ b/llvm/test/CodeGen/ARM/stack-limit-variable-thumb1.ll
@@ -3,14 +3,20 @@
 
 ; The -fstack-limit-variable=<var> prologue is emitted only for functions that
 ; carry the "stack-limit-variable"="<var>" attribute and have a non-empty stack
-; frame. It loads <var>, computes the free space (sp - limit), compares it
-; against the frame size and traps with "svc #255" when it is too small.
-; Thumb1 has no movw/movt or wide encodings, so the sequence loads the limit's
-; address from the constant pool and does the arithmetic in two low registers
-; (r2/r3). Under AAPCS arguments are assigned r0, r1, r2, r3 in order, so
-; r2/r3 are the argument registers least likely to be live on entry. Each is
-; used directly if dead and preserved with push/pop only if it still carries a
-; live argument.
+; frame. Thumb1 has no movw/movt or wide encodings and no "sub" that reads sp,
+; so the sequence loads <var> through the constant pool, computes the lowest
+; admissible sp value (limit + frame size) and compares sp against it directly
+; (ARMv6-M permits sp as the first operand of a register-register cmp),
+; trapping with "svc" when the stack is too small. Small frames need one
+; low scratch register (r3), large frames a second one (r2) to hold the frame
+; size. Under AAPCS arguments are assigned r0, r1, r2, r3 in order, so r2/r3
+; are the argument registers least likely to be live on entry. A scratch is
+; used directly if dead. The first live one is stashed in r12 with mov (no
+; stack traffic), and push/pop is a fallback used only when a large-frame
+; check requires both r2 and r3. In that fallback the check is split in two:
+; sp is first compared against the limit itself, so the push runs only when sp
+; is provably above the limit and never writes below it. (A function where r12
+; itself is live on entry is rejected. see 
stack-limit-variable-diagnostics.ll.)
 
 declare void @sink(ptr)
 
@@ -18,11 +24,10 @@ declare void @sink(ptr)
 define void @needs_check() #0 {
 ; CHECK-LABEL: needs_check:
 ; CHECK:       @ %bb.0:
-; CHECK-NEXT:    mov r3, sp
-; CHECK-NEXT:    ldr r2, .LCPI0_0
-; CHECK-NEXT:    ldr r2, [r2]
-; CHECK-NEXT:    sub r3, r3, r2
-; CHECK-NEXT:    cmp r3, #72
+; CHECK-NEXT:    ldr r3, .LCPI0_0
+; CHECK-NEXT:    ldr r3, [r3]
+; CHECK-NEXT:    adds r3, #72
+; CHECK-NEXT:    cmp sp, r3
 ; CHECK-NEXT:    bhs .LBB0_2
 ; CHECK-NEXT:  @ %bb.1:
 ; CHECK-NEXT:    svc #255
@@ -50,12 +55,11 @@ entry:
 define void @large_frame() #0 {
 ; CHECK-LABEL: large_frame:
 ; CHECK:       @ %bb.0:
-; CHECK-NEXT:    mov r3, sp
-; CHECK-NEXT:    ldr r2, .LCPI1_0
-; CHECK-NEXT:    ldr r2, [r2]
-; CHECK-NEXT:    sub r3, r3, r2
+; CHECK-NEXT:    ldr r3, .LCPI1_0
+; CHECK-NEXT:    ldr r3, [r3]
 ; CHECK-NEXT:    ldr r2, .LCPI1_1
-; CHECK-NEXT:    cmp r3, r2
+; CHECK-NEXT:    adds r3, r3, r2
+; CHECK-NEXT:    cmp sp, r3
 ; CHECK-NEXT:    bhs .LBB1_2
 ; CHECK-NEXT:  @ %bb.1:
 ; CHECK-NEXT:    svc #255
@@ -90,11 +94,10 @@ entry:
 define void @custom_var() #1 {
 ; CHECK-LABEL: custom_var:
 ; CHECK:       @ %bb.0:
-; CHECK-NEXT:    mov r3, sp
-; CHECK-NEXT:    ldr r2, .LCPI2_0
-; CHECK-NEXT:    ldr r2, [r2]
-; CHECK-NEXT:    sub r3, r3, r2
-; CHECK-NEXT:    cmp r3, #72
+; CHECK-NEXT:    ldr r3, .LCPI2_0
+; CHECK-NEXT:    ldr r3, [r3]
+; CHECK-NEXT:    adds r3, #72
+; CHECK-NEXT:    cmp sp, r3
 ; CHECK-NEXT:    bhs .LBB2_2
 ; CHECK-NEXT:  @ %bb.1:
 ; CHECK-NEXT:    svc #255
@@ -121,11 +124,10 @@ entry:
 define void @custom_trap() #2 {
 ; CHECK-LABEL: custom_trap:
 ; CHECK:       @ %bb.0:
-; CHECK-NEXT:    mov r3, sp
-; CHECK-NEXT:    ldr r2, .LCPI3_0
-; CHECK-NEXT:    ldr r2, [r2]
-; CHECK-NEXT:    sub r3, r3, r2
-; CHECK-NEXT:    cmp r3, #72
+; CHECK-NEXT:    ldr r3, .LCPI3_0
+; CHECK-NEXT:    ldr r3, [r3]
+; CHECK-NEXT:    adds r3, #72
+; CHECK-NEXT:    cmp sp, r3
 ; CHECK-NEXT:    bhs .LBB3_2
 ; CHECK-NEXT:  @ %bb.1:
 ; CHECK-NEXT:    svc #42
@@ -148,18 +150,18 @@ entry:
   ret void
 }
 
-; All four argument registers are live on entry, so both scratch registers
-; (r2, r3) are preserved with push/pop around the check.
+; All four argument registers are live on entry. The small frame needs only
+; one scratch register (r3), which is stashed in r12 around the check; nothing
+; is written to the stack.
 define i32 @args_live(i32 %a, i32 %b, i32 %c, i32 %d) #0 {
 ; CHECK-LABEL: args_live:
 ; CHECK:       @ %bb.0:
-; CHECK-NEXT:    push {r2, r3}
-; CHECK-NEXT:    mov r3, sp
-; CHECK-NEXT:    ldr r2, .LCPI4_0
-; CHECK-NEXT:    ldr r2, [r2]
-; CHECK-NEXT:    sub r3, r3, r2
-; CHECK-NEXT:    cmp r3, #88
-; CHECK-NEXT:    pop {r2, r3}
+; CHECK-NEXT:    mov r12, r3
+; CHECK-NEXT:    ldr r3, .LCPI4_0
+; CHECK-NEXT:    ldr r3, [r3]
+; CHECK-NEXT:    adds r3, #88
+; CHECK-NEXT:    cmp sp, r3
+; CHECK-NEXT:    mov r3, r12
 ; CHECK-NEXT:    bhs .LBB4_2
 ; CHECK-NEXT:  @ %bb.1:
 ; CHECK-NEXT:    svc #255
@@ -192,18 +194,16 @@ entry:
   ret i32 %r
 }
 
-; Three argument registers are live (r0, r1, r2); only r2 collides with the
-; scratch registers, so just r2 is preserved and r3 is used directly.
+; Three argument registers are live (r0, r1, r2). The only scratch register
+; the small frame needs (r3) is dead, so it is clobbered directly with no
+; save at all.
 define i32 @three_args(i32 %a, i32 %b, i32 %c) #0 {
 ; CHECK-LABEL: three_args:
 ; CHECK:       @ %bb.0:
-; CHECK-NEXT:    push {r2}
-; CHECK-NEXT:    mov r3, sp
-; CHECK-NEXT:    ldr r2, .LCPI5_0
-; CHECK-NEXT:    ldr r2, [r2]
-; CHECK-NEXT:    sub r3, r3, r2
-; CHECK-NEXT:    cmp r3, #80
-; CHECK-NEXT:    pop {r2}
+; CHECK-NEXT:    ldr r3, .LCPI5_0
+; CHECK-NEXT:    ldr r3, [r3]
+; CHECK-NEXT:    adds r3, #80
+; CHECK-NEXT:    cmp sp, r3
 ; CHECK-NEXT:    bhs .LBB5_2
 ; CHECK-NEXT:  @ %bb.1:
 ; CHECK-NEXT:    svc #255
@@ -233,6 +233,123 @@ entry:
   ret i32 %r
 }
 
+; A large frame with three argument registers live (r0, r1, r2). Both scratch
+; registers are needed, but only r2 is live, so it is stashed in r12 and no
+; push is required.
+define i32 @large_frame_three_args(i32 %a, i32 %b, i32 %c) #0 {
+; CHECK-LABEL: large_frame_three_args:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    mov r12, r2
+; CHECK-NEXT:    ldr r3, .LCPI6_0
+; CHECK-NEXT:    ldr r3, [r3]
+; CHECK-NEXT:    ldr r2, .LCPI6_1
+; CHECK-NEXT:    adds r3, r3, r2
+; CHECK-NEXT:    cmp sp, r3
+; CHECK-NEXT:    mov r2, r12
+; CHECK-NEXT:    bhs .LBB6_2
+; CHECK-NEXT:  @ %bb.1:
+; CHECK-NEXT:    svc #255
+; CHECK-NEXT:  .LBB6_2: @ %entry
+; CHECK-NEXT:    .save {r4, r5, r6, lr}
+; CHECK-NEXT:    push {r4, r5, r6, lr}
+; CHECK-NEXT:    .pad #508
+; CHECK-NEXT:    sub sp, #508
+; CHECK-NEXT:    .pad #508
+; CHECK-NEXT:    sub sp, #508
+; CHECK-NEXT:    .pad #16
+; CHECK-NEXT:    sub sp, #16
+; CHECK-NEXT:    mov r4, r2
+; CHECK-NEXT:    mov r5, r1
+; CHECK-NEXT:    mov r6, r0
+; CHECK-NEXT:    add r0, sp, #8
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    adds r0, r6, r5
+; CHECK-NEXT:    adds r0, r0, r4
+; CHECK-NEXT:    add sp, #508
+; CHECK-NEXT:    add sp, #508
+; CHECK-NEXT:    add sp, #16
+; CHECK-NEXT:    pop {r4, r5, r6, pc}
+; CHECK-NEXT:    .p2align 2
+; CHECK-NEXT:  @ %bb.3:
+; CHECK-NEXT:  .LCPI6_0:
+; CHECK-NEXT:    .long __stack_boundary
+; CHECK-NEXT:  .LCPI6_1:
+; CHECK-NEXT:    .long 1048 @ 0x418
+entry:
+  %buf = alloca [1024 x i8], align 1
+  call void @sink(ptr %buf)
+  %t0 = add i32 %a, %b
+  %r = add i32 %t0, %c
+  ret i32 %r
+}
+
+; A large frame with all four argument registers live. Both scratch registers
+; are needed and both are live, but r12 is a single slot, so r3 goes to r12
+; and r2 falls back to a push/pop. The check is split in two: sp is first
+; compared against the limit itself and traps if the stack is exhausted, and
+; only then is r2 pushed (provably at or above the limit) and the frame-size
+; check run. Both paths share one restore of r3 before the final branch, which
+; is bhi so that a single condition suits the flags of either comparison. The
+; frame size is rounded up to a multiple of 4 and biased by -8 (1048 -> 1040
+; here). The rationale of the bias is that checking
+; sp - 4 > limit + alignedSize - 8 is equivalent to checking
+; sp >= limit + alignedSize.
+define i32 @large_frame_args_live(i32 %a, i32 %b, i32 %c, i32 %d) #0 {
+; CHECK-LABEL: large_frame_args_live:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    mov r12, r3
+; CHECK-NEXT:    ldr r3, .LCPI7_0
+; CHECK-NEXT:    ldr r3, [r3]
+; CHECK-NEXT:    cmp sp, r3
+; CHECK-NEXT:    bls .LBB7_2
+; CHECK-NEXT:  @ %bb.1:
+; CHECK-NEXT:    push {r2}
+; CHECK-NEXT:    ldr r2, .LCPI7_1
+; CHECK-NEXT:    adds r3, r3, r2
+; CHECK-NEXT:    cmp sp, r3
+; CHECK-NEXT:    pop {r2}
+; CHECK-NEXT:  .LBB7_2:
+; CHECK-NEXT:    mov r3, r12
+; CHECK-NEXT:    bhi .LBB7_4
+; CHECK-NEXT:  @ %bb.3:
+; CHECK-NEXT:    svc #255
+; CHECK-NEXT:  .LBB7_4: @ %entry
+; CHECK-NEXT:    .save {r4, r5, r6, r7, lr}
+; CHECK-NEXT:    push {r4, r5, r6, r7, lr}
+; CHECK-NEXT:    .pad #508
+; CHECK-NEXT:    sub sp, #508
+; CHECK-NEXT:    .pad #508
+; CHECK-NEXT:    sub sp, #508
+; CHECK-NEXT:    .pad #12
+; CHECK-NEXT:    sub sp, #12
+; CHECK-NEXT:    mov r4, r3
+; CHECK-NEXT:    mov r5, r2
+; CHECK-NEXT:    mov r6, r1
+; CHECK-NEXT:    mov r7, r0
+; CHECK-NEXT:    add r0, sp, #4
+; CHECK-NEXT:    bl sink
+; CHECK-NEXT:    adds r0, r5, r4
+; CHECK-NEXT:    adds r1, r7, r6
+; CHECK-NEXT:    adds r0, r1, r0
+; CHECK-NEXT:    add sp, #508
+; CHECK-NEXT:    add sp, #508
+; CHECK-NEXT:    add sp, #12
+; CHECK-NEXT:    pop {r4, r5, r6, r7, pc}
+; CHECK-NEXT:    .p2align 2
+; CHECK-NEXT:  @ %bb.5:
+; CHECK-NEXT:  .LCPI7_0:
+; CHECK-NEXT:    .long __stack_boundary
+; CHECK-NEXT:  .LCPI7_1:
+; CHECK-NEXT:    .long 1040 @ 0x410
+entry:
+  %buf = alloca [1024 x i8], align 1
+  call void @sink(ptr %buf)
+  %t0 = add i32 %a, %b
+  %t1 = add i32 %c, %d
+  %r = add i32 %t0, %t1
+  ret i32 %r
+}
+
 ; No attribute: no check is emitted.
 define void @no_attr() {
 ; CHECK-LABEL: no_attr:
diff --git a/llvm/test/CodeGen/ARM/stack-limit-variable-thumb2.ll 
b/llvm/test/CodeGen/ARM/stack-limit-variable-thumb2.ll
index 61915e32e3a5b..37692c1eb84e2 100644
--- a/llvm/test/CodeGen/ARM/stack-limit-variable-thumb2.ll
+++ b/llvm/test/CodeGen/ARM/stack-limit-variable-thumb2.ll
@@ -4,10 +4,10 @@
 
 ; The -fstack-limit-variable=<var> prologue is emitted only for functions that
 ; carry the "stack-limit-variable"="<var>" attribute and have a non-empty stack
-; frame. It loads <var>, computes the free space (sp - limit), compares it
-; against the frame size and traps with "svc #255" when it is too small.
-; The Thumb2 sequence is identical on every Thumb2 (Cortex-M3 and later)
-; target, so both triples share the same checks.
+; frame. It loads <var>, adds the frame size to form the lowest admissible SP
+; value, compares SP against it and traps with "svc #255" when the stack is
+; too small. The Thumb2 sequence is identical on every Thumb2 (Cortex-M3 and
+; later) target, so both triples share the same checks.
 
 declare void @sink(ptr)
 
@@ -18,8 +18,8 @@ define void @needs_check() #0 {
 ; CHECK-NEXT:    movw r12, :lower16:__stack_boundary
 ; CHECK-NEXT:    movt r12, :upper16:__stack_boundary
 ; CHECK-NEXT:    ldr.w r12, [r12]
-; CHECK-NEXT:    sub.w r12, sp, r12
-; CHECK-NEXT:    cmp.w r12, #72
+; CHECK-NEXT:    add.w r12, r12, #72
+; CHECK-NEXT:    cmp sp, r12
 ; CHECK-NEXT:    it lo
 ; CHECK-NEXT:    svclo #255
 ; CHECK-NEXT:    .save {r7, lr}
@@ -44,8 +44,8 @@ define void @custom_var() #1 {
 ; CHECK-NEXT:    movw r12, :lower16:my_limit
 ; CHECK-NEXT:    movt r12, :upper16:my_limit
 ; CHECK-NEXT:    ldr.w r12, [r12]
-; CHECK-NEXT:    sub.w r12, sp, r12
-; CHECK-NEXT:    cmp.w r12, #72
+; CHECK-NEXT:    add.w r12, r12, #72
+; CHECK-NEXT:    cmp sp, r12
 ; CHECK-NEXT:    it lo
 ; CHECK-NEXT:    svclo #255
 ; CHECK-NEXT:    .save {r7, lr}
@@ -71,8 +71,8 @@ define void @large_frame() #0 {
 ; CHECK-NEXT:    movw r12, :lower16:__stack_boundary
 ; CHECK-NEXT:    movt r12, :upper16:__stack_boundary
 ; CHECK-NEXT:    ldr.w r12, [r12]
-; CHECK-NEXT:    sub.w r12, sp, r12
-; CHECK-NEXT:    cmp.w r12, #4016
+; CHECK-NEXT:    add.w r12, r12, #4016
+; CHECK-NEXT:    cmp sp, r12
 ; CHECK-NEXT:    it lo
 ; CHECK-NEXT:    svclo #255
 ; CHECK-NEXT:    .save {r7, lr}
@@ -125,8 +125,8 @@ define void @custom_trap() #2 {
 ; CHECK-NEXT:    movw r12, :lower16:__stack_boundary
 ; CHECK-NEXT:    movt r12, :upper16:__stack_boundary
 ; CHECK-NEXT:    ldr.w r12, [r12]
-; CHECK-NEXT:    sub.w r12, sp, r12
-; CHECK-NEXT:    cmp.w r12, #72
+; CHECK-NEXT:    add.w r12, r12, #72
+; CHECK-NEXT:    cmp sp, r12
 ; CHECK-NEXT:    it lo
 ; CHECK-NEXT:    svclo #42
 ; CHECK-NEXT:    .save {r7, lr}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to