[llvm] [SystemZ][z/OS] Emit CELQMAIN for a module defining main (PR #226710)

MMS IT GmbH via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 29 12:53:10 PDT 2026


https://github.com/mms-it-ch updated https://github.com/llvm/llvm-project/pull/226710

>From a8c29c3751b27392ce5ffc8d7e973682541e78c7 Mon Sep 17 00:00:00 2001
From: mms-it-ch <info at mms-it.ch>
Date: Sat, 26 Sep 2026 14:01:10 +0200
Subject: [PATCH 1/2] [SystemZ][z/OS] Emit CELQMAIN for a module defining main

The Language Environment startup routine CELQSTRT finds the main routine and
its environment through CELQMAIN. The copy of CELQMAIN in SCEEBND2 is in NORENT
format and refers to the symbol MAIN, so a program built with clang could not
start (U4093 reason 218 or a protection exception). Emit CELQMAIN in RENT format
for a module which defines main, together with a reference to the bootstrap
routine CELQBST, which CELQSTRT only references weakly.

Assisted-by: Claude Code (Anthropic)
---
 .../SystemZ/SystemZXPLINKAsmPrinter.cpp       | 52 +++++++++++++++++++
 .../Target/SystemZ/SystemZXPLINKAsmPrinter.h  |  1 +
 .../CodeGen/SystemZ/zos-celqmain-nomain.ll    | 13 +++++
 llvm/test/CodeGen/SystemZ/zos-celqmain.ll     | 30 +++++++++++
 4 files changed, 96 insertions(+)
 create mode 100644 llvm/test/CodeGen/SystemZ/zos-celqmain-nomain.ll
 create mode 100644 llvm/test/CodeGen/SystemZ/zos-celqmain.ll

diff --git a/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.cpp b/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.cpp
index 3ab3dfc4a0bcbd..5f8c2de88c2a4c 100644
--- a/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.cpp
@@ -272,6 +272,11 @@ void SystemZXPLINKAsmPrinter::emitXXStructorList(const DataLayout &DL,
 
 void SystemZXPLINKAsmPrinter::emitEndOfAsmFile(Module &M) {
   auto *ZOS = getTargetStreamer();
+  // A main program needs CELQMAIN, through which the Language Environment
+  // startup (CELQSTRT) finds the main routine and its environment.
+  if (const Function *MainFn = M.getFunction("main");
+      MainFn && !MainFn->isDeclaration())
+    emitCELQMAIN(*MainFn);
   emitADASection();
   emitIDRLSection(M);
   // On z/OS, we need to associate an external data reference with an ED
@@ -289,6 +294,53 @@ void SystemZXPLINKAsmPrinter::emitEndOfAsmFile(Module &M) {
   }
 }
 
+// Emit CELQMAIN in RENT format (see z/OS Language Environment Vendor
+// Interfaces, "Program initialization and termination for AMODE 64
+// applications"):
+//   +0   X'04000001'
+//   +8   AD(main entry point)
+//   +10  AD(CELQINPL)
+//   +18  A(0) / Q(environment); emitted as 8 byte R-con to main, which the
+//        binder resolves to the offset of the ADA of main (fits into 4 bytes)
+// followed by a reference to the bootstrap routine CELQBST. CELQSTRT only has
+// a weak reference to CELQBST, so without it autocall would not include it.
+void SystemZXPLINKAsmPrinter::emitCELQMAIN(const Function &MainFn) {
+  MCSymbol *MainSym = getSymbol(&MainFn);
+  auto ExternalOSSymbol = [&](StringRef Name) {
+    MCSymbol *Sym = OutContext.getOrCreateSymbol(Name);
+    OutStreamer->emitSymbolAttribute(Sym, MCSA_OSLinkage);
+    OutStreamer->emitSymbolAttribute(Sym, MCSA_Global);
+    return Sym;
+  };
+  MCSymbol *CELQINPL = ExternalOSSymbol("CELQINPL");
+  MCSymbol *CELQBST = ExternalOSSymbol("CELQBST");
+
+  OutStreamer->pushSection();
+  OutStreamer->switchSection(getObjFileLowering().getTextSection());
+  OutStreamer->emitValueToAlignment(Align(8));
+  MCSymbol *CELQMAIN = OutContext.getOrCreateSymbol("CELQMAIN");
+  // Referenced by CELQSTRT, CELQINPL and CELQBST with OS linkage.
+  OutStreamer->emitSymbolAttribute(CELQMAIN, MCSA_OSLinkage);
+  OutStreamer->emitSymbolAttribute(CELQMAIN, MCSA_Global);
+  OutStreamer->emitSymbolAttribute(CELQMAIN, MCSA_Hidden);
+  OutStreamer->emitLabel(CELQMAIN);
+  OutStreamer->AddComment("CELQMAIN, RENT format");
+  OutStreamer->emitInt32(0x04000001);
+  OutStreamer->emitInt32(0);
+  OutStreamer->AddComment("Address of main");
+  OutStreamer->emitValue(MCSymbolRefExpr::create(MainSym, OutContext), 8);
+  OutStreamer->AddComment("Address of CELQINPL");
+  OutStreamer->emitValue(MCSymbolRefExpr::create(CELQINPL, OutContext), 8);
+  OutStreamer->AddComment("Q(environment) of main");
+  OutStreamer->emitValue(
+      MCSpecifierExpr::create(MCSymbolRefExpr::create(MainSym, OutContext),
+                              SystemZ::S_RCon, OutContext),
+      8);
+  OutStreamer->AddComment("Reference to CELQBST");
+  OutStreamer->emitValue(MCSymbolRefExpr::create(CELQBST, OutContext), 8);
+  OutStreamer->popSection();
+}
+
 void SystemZXPLINKAsmPrinter::emitADASection() {
   OutStreamer->pushSection();
 
diff --git a/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.h b/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.h
index b4deeb4fc262c0..c221c02faf36c0 100644
--- a/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.h
+++ b/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.h
@@ -98,6 +98,7 @@ class LLVM_LIBRARY_VISIBILITY SystemZXPLINKAsmPrinter
   void emitPPA2(Module &M);
   void emitADASection();
   void emitIDRLSection(Module &M);
+  void emitCELQMAIN(const Function &MainFn);
   void emitCallInformation(CallType CT);
 
   SystemZTargetzOSStreamer *getTargetStreamer() {
diff --git a/llvm/test/CodeGen/SystemZ/zos-celqmain-nomain.ll b/llvm/test/CodeGen/SystemZ/zos-celqmain-nomain.ll
new file mode 100644
index 00000000000000..c6cdc5b67c0668
--- /dev/null
+++ b/llvm/test/CodeGen/SystemZ/zos-celqmain-nomain.ll
@@ -0,0 +1,13 @@
+; A module without a definition of main gets no CELQMAIN.
+;
+; RUN: llc < %s -mtriple=s390x-ibm-zos | FileCheck %s
+
+; CHECK-NOT: CELQMAIN
+; CHECK-NOT: CELQBST
+
+declare signext i32 @main()
+
+define signext i32 @f() {
+  %r = call signext i32 @main()
+  ret i32 %r
+}
diff --git a/llvm/test/CodeGen/SystemZ/zos-celqmain.ll b/llvm/test/CodeGen/SystemZ/zos-celqmain.ll
new file mode 100644
index 00000000000000..04b41409dc6eba
--- /dev/null
+++ b/llvm/test/CodeGen/SystemZ/zos-celqmain.ll
@@ -0,0 +1,30 @@
+; A module that defines main gets CELQMAIN in RENT format, through which the
+; Language Environment startup routine CELQSTRT finds main and its ADA, plus a
+; reference to the bootstrap routine CELQBST.
+;
+; RUN: llc < %s -mtriple=s390x-ibm-zos | FileCheck %s
+; RUN: llc < %s -mtriple=s390x-ibm-zos -filetype=obj -o - | od -Ax -tx1 | FileCheck --check-prefix=OBJ %s
+
+; CHECK:      CELQMAIN XATTR LINKAGE(OS),SCOPE(LIBRARY)
+; CHECK-NEXT: CELQMAIN DS 0H
+; CHECK-NEXT: * CELQMAIN, RENT format
+; CHECK-NEXT:  DC XL4'04000001'
+; CHECK-NEXT:  DC XL4'00000000'
+; CHECK-NEXT: * Address of main
+; CHECK-NEXT:  DC AD(main)
+; CHECK-NEXT: * Address of CELQINPL
+; CHECK-NEXT:  DC AD(CELQINPL)
+; CHECK-NEXT: * Q(environment) of main
+; CHECK-NEXT:  DC RD(main)
+; CHECK-NEXT: * Reference to CELQBST
+; CHECK-NEXT:  DC AD(CELQBST)
+; CHECK:       EXTRN CELQINPL
+; CHECK:      CELQINPL XATTR LINKAGE(OS),SCOPE(EXPORT)
+; CHECK:       EXTRN CELQBST
+; CHECK:      CELQBST XATTR LINKAGE(OS),SCOPE(EXPORT)
+
+; OBJ: 04 00 00 01 00 00 00 00
+
+define signext i32 @main() {
+  ret i32 42
+}

>From 5a538af43aaa313aedd04babe24001c4305faa7d Mon Sep 17 00:00:00 2001
From: mms-it-ch <info at mms-it.ch>
Date: Tue, 29 Sep 2026 21:47:30 +0200
Subject: [PATCH 2/2] [SystemZ][z/OS] Emit CELQMAIN at the start of the file

With debug info, emitDebugARanges() can end the text section before
emitEndOfAsmFile() is called, so switching back to it for CELQMAIN hit the
"Section already ended" assertion. Emit CELQMAIN together with the PPA2 in
emitStartOfAsmFile(), while the text section is still open, and add a test
with debug info and -generate-arange-section.

Assisted-by: Claude Code (Anthropic)
---
 .../SystemZ/SystemZXPLINKAsmPrinter.cpp       | 12 ++++----
 .../CodeGen/SystemZ/zos-celqmain-debug.ll     | 29 +++++++++++++++++++
 2 files changed, 36 insertions(+), 5 deletions(-)
 create mode 100755 llvm/test/CodeGen/SystemZ/zos-celqmain-debug.ll

diff --git a/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.cpp b/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.cpp
index 5f8c2de88c2a4c..b0733de040e235 100644
--- a/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.cpp
@@ -272,11 +272,6 @@ void SystemZXPLINKAsmPrinter::emitXXStructorList(const DataLayout &DL,
 
 void SystemZXPLINKAsmPrinter::emitEndOfAsmFile(Module &M) {
   auto *ZOS = getTargetStreamer();
-  // A main program needs CELQMAIN, through which the Language Environment
-  // startup (CELQSTRT) finds the main routine and its environment.
-  if (const Function *MainFn = M.getFunction("main");
-      MainFn && !MainFn->isDeclaration())
-    emitCELQMAIN(*MainFn);
   emitADASection();
   emitIDRLSection(M);
   // On z/OS, we need to associate an external data reference with an ED
@@ -683,6 +678,13 @@ void SystemZXPLINKAsmPrinter::calculatePPA1() {
 
 void SystemZXPLINKAsmPrinter::emitStartOfAsmFile(Module &M) {
   emitPPA2(M);
+  // A main program needs CELQMAIN, through which the Language Environment
+  // startup (CELQSTRT) finds the main routine and its environment. It is
+  // emitted here and not at the end of the file, because the text section
+  // may already be closed then (e.g. after emitting the DWARF aranges).
+  if (const Function *MainFn = M.getFunction("main");
+      MainFn && !MainFn->isDeclaration())
+    emitCELQMAIN(*MainFn);
   AsmPrinter::emitStartOfAsmFile(M);
 }
 
diff --git a/llvm/test/CodeGen/SystemZ/zos-celqmain-debug.ll b/llvm/test/CodeGen/SystemZ/zos-celqmain-debug.ll
new file mode 100755
index 00000000000000..ddcf9a52e25428
--- /dev/null
+++ b/llvm/test/CodeGen/SystemZ/zos-celqmain-debug.ll
@@ -0,0 +1,29 @@
+; CELQMAIN must be emitted while the text section is still open. With debug
+; info, the DWARF aranges end the text section before emitEndOfAsmFile.
+;
+; RUN: llc < %s -mtriple=s390x-ibm-zos -generate-arange-section | FileCheck %s
+; RUN: llc < %s -mtriple=s390x-ibm-zos -generate-arange-section \
+; RUN:   -filetype=obj -o - | od -Ax -tx1 | FileCheck --check-prefix=OBJ %s
+
+; CHECK:      CELQMAIN DS 0H
+; CHECK-NEXT: * CELQMAIN, RENT format
+; CHECK-NEXT:  DC XL4'04000001'
+; CHECK:      main DS 0H
+
+; OBJ: 04 00 00 01 00 00 00 00
+
+define signext i32 @main() !dbg !4 {
+  ret i32 42, !dbg !7
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, emissionKind: FullDebug)
+!1 = !DIFile(filename: "celqmain.c", directory: "/")
+!2 = !{i32 7, !"Dwarf Version", i32 4}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!5 = !DISubroutineType(types: !6)
+!6 = !{null}
+!7 = !DILocation(line: 1, column: 1, scope: !4)



More information about the llvm-commits mailing list