[clang] [SFrame][Retry] Add assembler option --gsframe (PR #165806)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jan 6 14:17:11 PST 2026
https://github.com/Sterling-Augustine updated https://github.com/llvm/llvm-project/pull/165806
>From 546da436c6ea1c1b7d01c975da6fb248fbce24d0 Mon Sep 17 00:00:00 2001
From: Sterling Augustine <saugustine at google.com>
Date: Thu, 30 Oct 2025 15:50:20 -0700
Subject: [PATCH 1/3] This plumbs the option --gsframe through the various
levels needed to support it in the assembler.
This is the final step in assembler-level sframe support for x86. With
it in place, clang produces sframe-sections that successfully link
with gnu-ld.
LLD support is pending some discussion.
---
clang/include/clang/Basic/CodeGenOptions.def | 2 ++
clang/include/clang/Driver/Options.td | 5 +++++
clang/lib/CodeGen/BackendUtil.cpp | 1 +
clang/lib/Driver/ToolChains/Clang.cpp | 18 ++++++++++++++++++
clang/test/Driver/sframe.c | 15 +++++++++++++++
clang/test/Misc/cc1as-sframe.s | 8 ++++++++
clang/tools/driver/cc1as_main.cpp | 6 ++++++
7 files changed, 55 insertions(+)
create mode 100644 clang/test/Driver/sframe.c
create mode 100644 clang/test/Misc/cc1as-sframe.s
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index 90e1f8d1eb5e9..99199c3a6c34c 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -120,6 +120,8 @@ CODEGENOPT(StackSizeSection , 1, 0, Benign) ///< Set when -fstack-size-section
///< Set when -femit-compact-unwind-non-canonical is enabled.
CODEGENOPT(EmitCompactUnwindNonCanonical, 1, 0, Benign)
+CODEGENOPT(EmitSFrameUnwind, 1, 0, Benign) ///< Set when -sframe is enabled.
+
///< Set when -fxray-always-emit-customevents is enabled.
CODEGENOPT(XRayAlwaysEmitCustomEvents , 1, 0, Benign)
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index cb5cb888c6da7..a7274fbdeabf1 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -7729,6 +7729,11 @@ def massembler_fatal_warnings : Flag<["-"], "massembler-fatal-warnings">,
def crel : Flag<["--"], "crel">,
HelpText<"Enable CREL relocation format (ELF only)">,
MarshallingInfoFlag<CodeGenOpts<"Crel">>;
+// The leading 'g' is misleading. This is an unwind tables option, not
+// a debug option. But uses this name for gnu compatibility.
+def gsframe : Flag<["--"], "gsframe">,
+ HelpText<"Generate .sframe unwind sections (ELF only)">,
+ MarshallingInfoFlag<CodeGenOpts<"EmitSFrameUnwind">>;
def mmapsyms_implicit : Flag<["-"], "mmapsyms=implicit">,
HelpText<"Allow mapping symbol at section beginning to be implicit, "
"lowering number of mapping symbols at the expense of some "
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index 3c313149ca1fc..1f7a96af015b6 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -500,6 +500,7 @@ static bool initTargetOptions(const CompilerInstance &CI,
Options.MCOptions.EmitDwarfUnwind = CodeGenOpts.getEmitDwarfUnwind();
Options.MCOptions.EmitCompactUnwindNonCanonical =
CodeGenOpts.EmitCompactUnwindNonCanonical;
+ Options.MCOptions.EmitSFrameUnwind = CodeGenOpts.EmitSFrameUnwind;
Options.MCOptions.MCRelaxAll = CodeGenOpts.RelaxAll;
Options.MCOptions.MCSaveTempLabels = CodeGenOpts.SaveTempLabels;
Options.MCOptions.MCUseDwarfDirectory =
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 4e8f63ea49480..f40e68b5755ae 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -2626,6 +2626,14 @@ static void CollectArgsForIntegratedAssembler(Compilation &C,
llvm::codegenoptions::DebugInfoConstructor,
DwarfVersion, llvm::DebuggerKind::Default);
}
+ } else if (Value == "--gsframe") {
+ if (Triple.isOSBinFormatELF() && Triple.isX86()) {
+ CmdArgs.push_back("--gsframe");
+ } else {
+ D.Diag(diag::err_drv_unsupported_opt_for_target)
+ << Value << D.getTargetTriple();
+ break;
+ }
} else if (Value.starts_with("-mcpu") || Value.starts_with("-mfpu") ||
Value.starts_with("-mhwdiv") || Value.starts_with("-march")) {
// Do nothing, we'll validate it later.
@@ -5929,6 +5937,16 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
else if (UnwindTables)
CmdArgs.push_back("-funwind-tables=1");
+ // Sframe unwind tables are independent of the other types, and only defined
+ // for these two targets.
+ if (Arg *A = Args.getLastArg(options::OPT_gsframe)) {
+ if ((Triple.isAArch64() || Triple.isX86()) && Triple.isOSBinFormatELF())
+ CmdArgs.push_back("--gsframe");
+ else
+ D.Diag(diag::err_drv_unsupported_opt_for_target)
+ << A->getOption().getName() << TripleStr;
+ }
+
// Prepare `-aux-target-cpu` and `-aux-target-feature` unless
// `--gpu-use-aux-triple-only` is specified.
if (!Args.getLastArg(options::OPT_gpu_use_aux_triple_only) &&
diff --git a/clang/test/Driver/sframe.c b/clang/test/Driver/sframe.c
new file mode 100644
index 0000000000000..6c0969e9042a8
--- /dev/null
+++ b/clang/test/Driver/sframe.c
@@ -0,0 +1,15 @@
+// RUN: %clang -### -c --target=x86_64 -Wa,--gsframe %s -Werror 2>&1 | FileCheck %s
+// CHECK: "-cc1" {{.*}}"--gsframe"
+
+// RUN: %clang -### -c --target=x86_64 %s 2>&1 | FileCheck %s --check-prefix=NO
+// NO: "-cc1"
+
+// RUN: %clang -### -c --target=x86_64 -Werror -Wa,--gsframe -x assembler %s -Werror 2>&1 | FileCheck %s --check-prefix=ASM
+// ASM: "-cc1as" {{.*}}"--gsframe"
+
+// RUN: not %clang -### -c --target=mips64 -Wa,--gsframe %s 2>&1 | FileCheck %s --check-prefix=NOTARGETC
+// NOTARGETC: error: unsupported option '--gsframe' for target '{{.*}}'
+
+// RUN: not %clang -### -c --target=mips64 -Wa,--gsframe -x assembler %s 2>&1 | FileCheck %s --check-prefix=NOTARGETASM
+// NOTARGETASM: error: unsupported option '--gsframe' for target '{{.*}}'
+
diff --git a/clang/test/Misc/cc1as-sframe.s b/clang/test/Misc/cc1as-sframe.s
new file mode 100644
index 0000000000000..9d16f0ada32ad
--- /dev/null
+++ b/clang/test/Misc/cc1as-sframe.s
@@ -0,0 +1,8 @@
+// REQUIRES: x86-registered-target
+// RUN: %clang -cc1as -triple x86_64 %s -filetype obj --gsframe -o %t.o
+// RUN: llvm-readelf -S %t.o | FileCheck %s
+
+// CHECK: .sframe
+.cfi_startproc
+ call foo
+.cfi_endproc
diff --git a/clang/tools/driver/cc1as_main.cpp b/clang/tools/driver/cc1as_main.cpp
index 50da2f8449a22..3187a5b5c571b 100644
--- a/clang/tools/driver/cc1as_main.cpp
+++ b/clang/tools/driver/cc1as_main.cpp
@@ -163,6 +163,10 @@ struct AssemblerInvocation {
LLVM_PREFERRED_TYPE(bool)
unsigned EmitCompactUnwindNonCanonical : 1;
+ // Whether to emit sframe unwind sections.
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned EmitSFrameUnwind : 1;
+
LLVM_PREFERRED_TYPE(bool)
unsigned Crel : 1;
LLVM_PREFERRED_TYPE(bool)
@@ -388,6 +392,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.EmitCompactUnwindNonCanonical =
Args.hasArg(OPT_femit_compact_unwind_non_canonical);
+ Opts.EmitSFrameUnwind = Args.hasArg(OPT_gsframe);
Opts.Crel = Args.hasArg(OPT_crel);
Opts.ImplicitMapsyms = Args.hasArg(OPT_mmapsyms_implicit);
Opts.X86RelaxRelocations = !Args.hasArg(OPT_mrelax_relocations_no);
@@ -450,6 +455,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
MCOptions.MCRelaxAll = Opts.RelaxAll;
MCOptions.EmitDwarfUnwind = Opts.EmitDwarfUnwind;
MCOptions.EmitCompactUnwindNonCanonical = Opts.EmitCompactUnwindNonCanonical;
+ MCOptions.EmitSFrameUnwind = Opts.EmitSFrameUnwind;
MCOptions.MCSaveTempLabels = Opts.SaveTemporaryLabels;
MCOptions.Crel = Opts.Crel;
MCOptions.ImplicitMapSyms = Opts.ImplicitMapsyms;
>From c2b7871a70c74611e1c7966e94552d37b55c0af7 Mon Sep 17 00:00:00 2001
From: Sterling Augustine <saugustine at google.com>
Date: Mon, 3 Nov 2025 09:44:55 -0800
Subject: [PATCH 2/3] Tighten test cases; fix comment and aarch64 clause.
---
clang/lib/Driver/ToolChains/Clang.cpp | 6 +++---
clang/test/Driver/sframe.c | 10 ++++++----
clang/test/Misc/cc1as-sframe.s | 3 +--
3 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index f40e68b5755ae..9c7f1fc8bd2c3 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -5937,10 +5937,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
else if (UnwindTables)
CmdArgs.push_back("-funwind-tables=1");
- // Sframe unwind tables are independent of the other types, and only defined
- // for these two targets.
+ // Sframe unwind tables are independent of the other types. Although also
+ // defined for aarch64, only x86_64 support is implemented at the moment.
if (Arg *A = Args.getLastArg(options::OPT_gsframe)) {
- if ((Triple.isAArch64() || Triple.isX86()) && Triple.isOSBinFormatELF())
+ if (Triple.isOSBinFormatELF() && Triple.isX86())
CmdArgs.push_back("--gsframe");
else
D.Diag(diag::err_drv_unsupported_opt_for_target)
diff --git a/clang/test/Driver/sframe.c b/clang/test/Driver/sframe.c
index 6c0969e9042a8..88fa6ebc6aacb 100644
--- a/clang/test/Driver/sframe.c
+++ b/clang/test/Driver/sframe.c
@@ -1,11 +1,13 @@
// RUN: %clang -### -c --target=x86_64 -Wa,--gsframe %s -Werror 2>&1 | FileCheck %s
-// CHECK: "-cc1" {{.*}}"--gsframe"
+// CHECK: "-cc1"
+// CHECK-SAME: "--gsframe"
-// RUN: %clang -### -c --target=x86_64 %s 2>&1 | FileCheck %s --check-prefix=NO
-// NO: "-cc1"
+// RUN: %clang -### -c --target=x86_64 %s 2>&1 | FileCheck %s --check-prefix=NO-GSFRAME
+// NO-GSFRAME-NOT: "--gsframe"
// RUN: %clang -### -c --target=x86_64 -Werror -Wa,--gsframe -x assembler %s -Werror 2>&1 | FileCheck %s --check-prefix=ASM
-// ASM: "-cc1as" {{.*}}"--gsframe"
+// ASM: "-cc1as"
+// ASM-SAME: "--gsframe"
// RUN: not %clang -### -c --target=mips64 -Wa,--gsframe %s 2>&1 | FileCheck %s --check-prefix=NOTARGETC
// NOTARGETC: error: unsupported option '--gsframe' for target '{{.*}}'
diff --git a/clang/test/Misc/cc1as-sframe.s b/clang/test/Misc/cc1as-sframe.s
index 9d16f0ada32ad..23f3d7b40f171 100644
--- a/clang/test/Misc/cc1as-sframe.s
+++ b/clang/test/Misc/cc1as-sframe.s
@@ -1,6 +1,5 @@
// REQUIRES: x86-registered-target
-// RUN: %clang -cc1as -triple x86_64 %s -filetype obj --gsframe -o %t.o
-// RUN: llvm-readelf -S %t.o | FileCheck %s
+// RUN: %clang -cc1as -triple x86_64 %s -filetype obj --gsframe | llvm-readelf -S - | FileCheck %s
// CHECK: .sframe
.cfi_startproc
>From 9612957259775cb7f6687c479475cbc49cfb662f Mon Sep 17 00:00:00 2001
From: Sterling Augustine <saugustine at google.com>
Date: Tue, 6 Jan 2026 14:16:29 -0800
Subject: [PATCH 3/3] Update for new main; add --allow-experimental-sframe
option.
---
.../clang/Basic/DiagnosticDriverKinds.td | 4 ++++
clang/lib/Driver/ToolChains/Clang.cpp | 22 +++++++++++++------
clang/test/Driver/sframe.c | 11 ++++++----
3 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index 83980e3ac35b7..fc4bc4c8db702 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -872,6 +872,10 @@ def err_drv_experimental_crel : Error<
"-Wa,--allow-experimental-crel must be specified to use -Wa,--crel. "
"CREL is experimental and uses a non-standard section type code">;
+def err_drv_experimental_sframe : Error<
+ "-Wa,--allow-experimental-sframe must be specified to use -Wa,--gsframe. "
+ "SFrames are experimental and may be removed at any time without warning">;
+
def warn_android_unversioned_fallback : Warning<
"using unversioned Android target directory %0 for target %1; unversioned "
"directories will not be used in Clang 19 -- provide a versioned directory "
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 9c7f1fc8bd2c3..ce626b60862ec 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -2411,6 +2411,7 @@ static void CollectArgsForIntegratedAssembler(Compilation &C,
const llvm::Triple &Triple = C.getDefaultToolChain().getTriple();
bool IsELF = Triple.isOSBinFormatELF();
bool Crel = false, ExperimentalCrel = false;
+ bool SFrame = false, ExperimentalSFrame = false;
bool ImplicitMapSyms = false;
bool UseRelaxRelocations = C.getDefaultToolChain().useRelaxRelocations();
bool UseNoExecStack = false;
@@ -2627,13 +2628,9 @@ static void CollectArgsForIntegratedAssembler(Compilation &C,
DwarfVersion, llvm::DebuggerKind::Default);
}
} else if (Value == "--gsframe") {
- if (Triple.isOSBinFormatELF() && Triple.isX86()) {
- CmdArgs.push_back("--gsframe");
- } else {
- D.Diag(diag::err_drv_unsupported_opt_for_target)
- << Value << D.getTargetTriple();
- break;
- }
+ SFrame = true;
+ } else if (Value == "--allow-experimental-sframe") {
+ ExperimentalSFrame = true;
} else if (Value.starts_with("-mcpu") || Value.starts_with("-mfpu") ||
Value.starts_with("-mhwdiv") || Value.starts_with("-march")) {
// Do nothing, we'll validate it later.
@@ -2687,6 +2684,17 @@ static void CollectArgsForIntegratedAssembler(Compilation &C,
<< "-Wa,--crel" << D.getTargetTriple();
}
}
+ if (SFrame) {
+ if (Triple.isOSBinFormatELF() && Triple.isX86()) {
+ if (!ExperimentalSFrame)
+ D.Diag(diag::err_drv_experimental_sframe);
+ else
+ CmdArgs.push_back("--gsframe");
+ } else {
+ D.Diag(diag::err_drv_unsupported_opt_for_target)
+ << "-Wa,--gsframe" << D.getTargetTriple();
+ }
+ }
if (ImplicitMapSyms)
CmdArgs.push_back("-mmapsyms=implicit");
if (Msa)
diff --git a/clang/test/Driver/sframe.c b/clang/test/Driver/sframe.c
index 88fa6ebc6aacb..b36dc71625c21 100644
--- a/clang/test/Driver/sframe.c
+++ b/clang/test/Driver/sframe.c
@@ -1,17 +1,20 @@
-// RUN: %clang -### -c --target=x86_64 -Wa,--gsframe %s -Werror 2>&1 | FileCheck %s
+// RUN: not %clang -### -c --target=x86_64 -Wa,--gsframe %s -Werror 2>&1 | FileCheck %s --check-prefix=NOEXP
+// NOEXP: error: -Wa,--allow-experimental-sframe must be specified to use -Wa,--gsframe. SFrames are experimental and may be removed at any time without warning
+
+// RUN: %clang -### -c --target=x86_64 -Wa,--gsframe -Wa,--allow-experimental-sframe %s -Werror 2>&1 | FileCheck %s
// CHECK: "-cc1"
// CHECK-SAME: "--gsframe"
// RUN: %clang -### -c --target=x86_64 %s 2>&1 | FileCheck %s --check-prefix=NO-GSFRAME
// NO-GSFRAME-NOT: "--gsframe"
-// RUN: %clang -### -c --target=x86_64 -Werror -Wa,--gsframe -x assembler %s -Werror 2>&1 | FileCheck %s --check-prefix=ASM
+// RUN: %clang -### -c --target=x86_64 -Werror -Wa,--gsframe,--allow-experimental-sframe -x assembler %s -Werror 2>&1 | FileCheck %s --check-prefix=ASM
// ASM: "-cc1as"
// ASM-SAME: "--gsframe"
// RUN: not %clang -### -c --target=mips64 -Wa,--gsframe %s 2>&1 | FileCheck %s --check-prefix=NOTARGETC
-// NOTARGETC: error: unsupported option '--gsframe' for target '{{.*}}'
+// NOTARGETC: error: unsupported option '-Wa,--gsframe' for target '{{.*}}'
// RUN: not %clang -### -c --target=mips64 -Wa,--gsframe -x assembler %s 2>&1 | FileCheck %s --check-prefix=NOTARGETASM
-// NOTARGETASM: error: unsupported option '--gsframe' for target '{{.*}}'
+// NOTARGETASM: error: unsupported option '-Wa,--gsframe' for target '{{.*}}'
More information about the cfe-commits
mailing list