[flang-commits] [flang] [llvm] [CodeGen] Take the executable stack from a module flag (PR #215152)

via flang-commits flang-commits at lists.llvm.org
Sun Aug 9 14:43:10 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-aarch64

Author: Jameson Nash (vtjnash)

<details>
<summary>Changes</summary>

AsmPrinter marked `.note.GNU-stack` executable whenever the module had a use of `llvm.init.trampoline`. This was not compliant with the documentation for the function, which causes regressions in Julia. Instead read a new `"executable-stack"` module flag, so that the request comes from the frontend that emitted the code rather than from scanning for an intrinsic and auto-magically (and wrongly) guessing.

Frontends generating code that needs an executable stack now have to set the flag for that purpose. For example, flang will do so from BoxedProcedurePass when it emits a stack based trampoline; the `-fsafe-trampoline` runtime pool does not need one (nor was the custom runtime intrinsics really necessary for `-fsafe-trampoline`, since the existing intrinsic was already defined to support that use case too).

Fixes a regression caused by #<!-- -->151754, which introduced new behavior onto the existing intrinsics which wasn't permitted by the documentation. Fixes issue with upgrading here: https://github.com/JuliaLang/julia/pull/62563#issuecomment-5220335991

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


9 Files Affected:

- (modified) flang/include/flang/Optimizer/CodeGen/CGPasses.td (+1) 
- (modified) flang/lib/Optimizer/CodeGen/BoxedProcedure.cpp (+35-1) 
- (modified) flang/test/Fir/boxproc-safe-trampoline.fir (+3) 
- (modified) flang/test/Fir/boxproc.fir (+4) 
- (modified) llvm/docs/LangRef.md (+18-4) 
- (modified) llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp (+8-7) 
- (modified) llvm/test/CodeGen/AArch64/trampoline.ll (+3-3) 
- (modified) llvm/test/CodeGen/RISCV/rv64-trampoline.ll (+4-4) 
- (added) llvm/test/CodeGen/X86/execstack-module-flag.ll (+29) 


``````````diff
diff --git a/flang/include/flang/Optimizer/CodeGen/CGPasses.td b/flang/include/flang/Optimizer/CodeGen/CGPasses.td
index 26cb7ba4b99c1..b1ad48f7b9c78 100644
--- a/flang/include/flang/Optimizer/CodeGen/CGPasses.td
+++ b/flang/include/flang/Optimizer/CodeGen/CGPasses.td
@@ -118,6 +118,7 @@ def BoxedProcedurePass : Pass<"boxed-procedure", "mlir::ModuleOp"> {
            "for W^X compliance. When enabled, internal procedure pointers "
            "use a runtime-managed pool of executable trampolines with "
            "separate data region, avoiding the need for an executable stack.">];
+  let dependentDialects = ["mlir::LLVM::LLVMDialect"];
 }
 
 def LowerRepackArraysPass : Pass<"lower-repack-arrays", "mlir::ModuleOp"> {
diff --git a/flang/lib/Optimizer/CodeGen/BoxedProcedure.cpp b/flang/lib/Optimizer/CodeGen/BoxedProcedure.cpp
index 1ec365664b295..1fb626a3666c5 100644
--- a/flang/lib/Optimizer/CodeGen/BoxedProcedure.cpp
+++ b/flang/lib/Optimizer/CodeGen/BoxedProcedure.cpp
@@ -16,6 +16,7 @@
 #include "flang/Optimizer/Dialect/Support/FIRContext.h"
 #include "flang/Optimizer/Support/FatalError.h"
 #include "flang/Optimizer/Support/InternalNames.h"
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
 #include "mlir/IR/PatternMatch.h"
 #include "mlir/Pass/Pass.h"
 #include "mlir/Transforms/DialectConversion.h"
@@ -192,6 +193,30 @@ class BoxprocTypeRewriter : public mlir::TypeConverter {
   mlir::Location loc;
 };
 
+/// Set the "executable-stack" LLVM module flag on \p module, which makes the
+/// backend mark the `.note.GNU-stack` section executable on ELF targets.
+static void requestExecutableStack(mlir::ModuleOp module) {
+  mlir::MLIRContext *context{module.getContext()};
+  auto flag{mlir::LLVM::ModuleFlagAttr::get(
+      context, mlir::LLVM::ModFlagBehavior::Max,
+      mlir::StringAttr::get(context, "executable-stack"),
+      mlir::IntegerAttr::get(mlir::IntegerType::get(context, 32), 1))};
+
+  // Append to the module flags that are already there, if any.
+  for (auto flagsOp : module.getOps<mlir::LLVM::ModuleFlagsOp>()) {
+    llvm::SmallVector<mlir::Attribute> flags{flagsOp.getFlags().getValue()};
+    if (llvm::is_contained(flags, flag))
+      return;
+    flags.push_back(flag);
+    flagsOp.setFlagsAttr(mlir::ArrayAttr::get(context, flags));
+    return;
+  }
+
+  mlir::OpBuilder builder{module.getBody(), module.getBody()->begin()};
+  mlir::LLVM::ModuleFlagsOp::create(builder, module.getLoc(),
+                                    builder.getArrayAttr({flag}));
+}
+
 /// A `boxproc` is an abstraction for a Fortran procedure reference. Typically,
 /// Fortran procedures can be referenced directly through a function pointer.
 /// However, Fortran has one-level dynamic scoping between a host procedure and
@@ -236,10 +261,16 @@ class BoxedProcedurePass
           processOp(op, rewriter, typeConverter);
         });
       }
+
+      if (needsExecutableStack)
+        requestExecutableStack(getModule());
     }
   }
 
 private:
+  /// Set when a stack based trampoline has been emitted.
+  bool needsExecutableStack = false;
+
   /// Trampoline handles collected while processing a function.
   /// Each entry is a Value representing the opaque handle returned
   /// by _FortranATrampolineInit, which must be freed before the
@@ -436,7 +467,10 @@ class BoxedProcedurePass
             rewriter.replaceOpWithNewOp<ConvertOp>(embox, toTy, callableAddr);
           }
         } else {
-          // Legacy stack-based trampoline path.
+          // Legacy stack-based trampoline path. The thunk is built in the host
+          // procedure's stack frame and jumped to, so request an executable
+          // stack.
+          needsExecutableStack = true;
           FirOpBuilder builder(rewriter, module);
           mlir::Type i8Ty{builder.getI8Type()};
           mlir::Type i8Ptr{builder.getRefType(i8Ty)};
diff --git a/flang/test/Fir/boxproc-safe-trampoline.fir b/flang/test/Fir/boxproc-safe-trampoline.fir
index de310bccaf19d..f4feec742cb16 100644
--- a/flang/test/Fir/boxproc-safe-trampoline.fir
+++ b/flang/test/Fir/boxproc-safe-trampoline.fir
@@ -5,6 +5,9 @@
 // and _FortranATrampolineFree instead of llvm.init.trampoline and
 // llvm.adjust.trampoline intrinsics.
 
+// The runtime trampoline pool needs no executable stack, hence no module flag.
+// CHECK-NOT:   llvm.module_flags
+
 // CHECK-LABEL: func.func @_QPtest_proc_dummy()
 // CHECK:         fir.zero_bits !fir.ref<i8>
 // CHECK:         fir.convert {{.*}} : {{.*}} -> !fir.ref<i8>
diff --git a/flang/test/Fir/boxproc.fir b/flang/test/Fir/boxproc.fir
index 8c8bf6a4607b6..f2cd1388974aa 100644
--- a/flang/test/Fir/boxproc.fir
+++ b/flang/test/Fir/boxproc.fir
@@ -146,6 +146,10 @@ func.func @_QPtest_proc_dummy_other(%arg0: !fir.boxproc<() -> ()>) {
 // CHECK:         ret { ptr, i64 } %[[VAL_44]]
 // CHECK:       }
 
+// The stack based trampolines request an executable stack via a module flag.
+// CHECK:       !llvm.module.flags = !{
+// CHECK:       = !{i32 7, !"executable-stack", i32 1}
+
 func.func @_QPtest_proc_dummy_char() {
   %c10 = arith.constant 10 : index
   %c0_i32 = arith.constant 0 : i32
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 3a616e8a29fcf..53e221c477e39 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -9525,6 +9525,15 @@ flags metadata, using the following key-value pairs:
 
 ### Other Module Flags
 
+`executable-stack`
+:   **Max**. If this flag is present and non-zero, the module contains code that
+    requires the stack to be executable, for example because it builds a
+    trampoline in stack memory and jumps to it. On ELF targets this is emitted as
+    a `.note.GNU-stack` section with the `SHF_EXECINSTR` flag set, which
+    instructs the linker to mark the stack of the resulting binary executable.
+    Frontends that generate such code are responsible for setting this flag;
+    when it is absent or zero, the stack is marked non-executable.
+
 `require-logical-pointer`
 :   This flag indicates this module must only use logical pointer intrinsics
     such as {ref}`@llvm.structured.gep <i_structured_gep>` or
@@ -21664,10 +21673,9 @@ These intrinsics make it possible to excise one parameter, marked with
 the {ref}`nest <nest>` attribute, from a function. The result is a
 callable function pointer lacking the nest parameter - the caller does
 not need to provide a value for it. Instead, the value to use is stored
-in advance in a "trampoline", a block of memory usually allocated on the
-stack, which also contains code to splice the nest value into the
-argument list. This is used to implement the GCC nested function address
-extension.
+in advance in a "trampoline", a block of memory which also contains code
+to splice the nest value into the argument list. This is used to
+implement the GCC nested function address extension.
 
 For example, if the function is `i32 f(ptr nest %c, i32 %x, i32 %y)`
 then the resulting function pointer has signature `i32 (i32, i32)`.
@@ -21707,6 +21715,12 @@ LLVM currently provides no portable way of determining them, so a
 front-end that generates this intrinsic needs to have some
 target-specific knowledge.
 
+The block may be allocated anywhere - the stack, the heap, a global, or a
+runtime-managed pool - as long as it is writable when
+`llvm.init.trampoline` executes and the address returned by
+{ref}`llvm.adjust.trampoline <int_at>` is executable when called. Those two
+addresses need not name the same mapping of the memory.
+
 The `func` argument must be a constant (potentially bitcasted) pointer to a
 function declaration or definition, since the calling convention may affect the
 content of the trampoline that is created.
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index 8f8ccd0d2b253..65be7a7846a71 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -3129,13 +3129,14 @@ bool AsmPrinter::doFinalization(Module &M) {
           ".note.GNU-no-split-stack", ELF::SHT_PROGBITS, 0));
   }
 
-  // If we don't have any trampolines, then we don't require stack memory
-  // to be executable. Some targets have a directive to declare this.
-  Function *InitTrampolineIntrinsic = M.getFunction("llvm.init.trampoline");
-  bool HasTrampolineUses =
-      InitTrampolineIntrinsic && !InitTrampolineIntrinsic->use_empty();
-  MCSection *S = MAI.getStackSection(OutContext, /*Exec=*/HasTrampolineUses);
-  if (S)
+  // Emit the section that tells the linker whether stack memory has to be
+  // executable, e.g. ELF's .note.GNU-stack. It is marked executable only if
+  // the module sets the "executable-stack" flag.
+  bool ExecStack = false;
+  if (auto *Val = mdconst::dyn_extract_or_null<ConstantInt>(
+          M.getModuleFlag("executable-stack")))
+    ExecStack = !Val->isZero();
+  if (MCSection *S = MAI.getStackSection(OutContext, ExecStack))
     OutStreamer->switchSection(S);
 
   if (TM.Options.EmitAddrsig) {
diff --git a/llvm/test/CodeGen/AArch64/trampoline.ll b/llvm/test/CodeGen/AArch64/trampoline.ll
index 12e3738b13c5b..7a8ce02cd7b48 100644
--- a/llvm/test/CodeGen/AArch64/trampoline.ll
+++ b/llvm/test/CodeGen/AArch64/trampoline.ll
@@ -264,8 +264,8 @@ define i64 @func2() {
   ret i64 0
 }
 
-; Check for the explicitly emitted .note.GNU-stack section (ELF only) in the
-; presence of trampolines.
+; Check for the explicitly emitted .note.GNU-stack section (ELF only). Marking
+; it executable takes the "executable-stack" module flag.
 ; UTC_ARGS: --disable
-; CHECK-LINUX:         .section        ".note.GNU-stack","x", at progbits
+; CHECK-LINUX:         .section        ".note.GNU-stack","", at progbits
 ; UTC_ARGS: --enable
diff --git a/llvm/test/CodeGen/RISCV/rv64-trampoline.ll b/llvm/test/CodeGen/RISCV/rv64-trampoline.ll
index 2ff26e5274542..fceb1a20bbb7b 100644
--- a/llvm/test/CodeGen/RISCV/rv64-trampoline.ll
+++ b/llvm/test/CodeGen/RISCV/rv64-trampoline.ll
@@ -77,9 +77,9 @@ define i64 @test0(i64 %n, ptr %p) nounwind {
 
 }
 
-; Check for the explicitly emitted .note.GNU-stack section (ELF only) in the
-; presence of trampolines.
+; Check for the explicitly emitted .note.GNU-stack section (ELF only). Marking
+; it executable takes the "executable-stack" module flag.
 ; UTC_ARGS: --disable
-; RV64-LINUX:         .section        ".note.GNU-stack","x", at progbits
-; RV64:               .section        ".note.GNU-stack","x", at progbits
+; RV64-LINUX:         .section        ".note.GNU-stack","", at progbits
+; RV64:               .section        ".note.GNU-stack","", at progbits
 ; UTC_ARGS: --enable
diff --git a/llvm/test/CodeGen/X86/execstack-module-flag.ll b/llvm/test/CodeGen/X86/execstack-module-flag.ll
new file mode 100644
index 0000000000000..85b0ec72e0d80
--- /dev/null
+++ b/llvm/test/CodeGen/X86/execstack-module-flag.ll
@@ -0,0 +1,29 @@
+;; The stack is marked executable only when the "executable-stack" module flag
+;; requests it, not when trampolines are present.
+
+; RUN: rm -rf %t && split-file %s %t
+; RUN: llc < %t/exec.ll -mtriple=x86_64-linux | FileCheck %s --check-prefix=EXEC
+; RUN: llc < %t/exec.ll -mtriple=amd64-solaris | FileCheck %s --check-prefix=NONE
+; RUN: llc < %t/zero.ll -mtriple=x86_64-linux | FileCheck %s --check-prefix=NOEXEC
+; RUN: llc < %t/trampoline.ll -mtriple=x86_64-linux | FileCheck %s --check-prefix=NOEXEC
+
+; EXEC:   .section	".note.GNU-stack","x", at progbits
+; NOEXEC: .section	".note.GNU-stack","", at progbits
+; NONE-NOT: .note.GNU-stack
+
+;--- exec.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 7, !"executable-stack", i32 1}
+
+;--- zero.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 7, !"executable-stack", i32 0}
+
+;--- trampoline.ll
+declare void @nested(ptr nest, i32)
+
+define ptr @f(ptr %tramp, ptr %nest) {
+  call void @llvm.init.trampoline(ptr %tramp, ptr @nested, ptr %nest)
+  %fp = call ptr @llvm.adjust.trampoline(ptr %tramp)
+  ret ptr %fp
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/215152


More information about the flang-commits mailing list