[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
Sat Sep 26 08:36:58 PDT 2026


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

The Language Environment startup routine `CELQSTRT` finds the main routine and its environment through `CELQMAIN`. clang does not emit it; the driver links with `-u CELQMAIN`, which pulls the copy from `SCEEBND2`. That copy is in NORENT format and refers to the symbol `MAIN`, so a program built with clang does not start (U4093 reason 218, or a protection exception because no WSA is allocated and the ADA offset is used as an absolute address).

For a module that defines `main`, emit `CELQMAIN` in RENT format as described in "z/OS Language Environment Vendor Interfaces", chapter "Program initialization and termination for AMODE 64 applications", plus a reference to the bootstrap routine `CELQBST`, which `CELQSTRT` only references weakly. `CELQMAIN` gets OS linkage, because `CELQSTRT`, `CELQINPL` and `CELQBST` reference it with OS linkage (otherwise the binder reports IEW2469E).

With this change `int main(void) { return 42; }` bound with `ld -e CELQSTRT` against `SCEEBND2` and the LE side decks, and no other objects, returns 42 on z/OS 3.1.

Tests: new `zos-celqmain.ll` and `zos-celqmain-nomain.ll`; `llvm-lit test/CodeGen/SystemZ test/MC/SystemZ test/MC/GOFF` passes (1305 passed, 19 unsupported).

The comment in `GOFFObjectWriter.cpp` mentions `emitCELQMAIN`; if an implementation is already planned upstream, I am happy to close this.

Assisted-by: Claude Code (Anthropic)

🤖 Generated with [Claude Code](https://claude.com/claude-code)


>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] [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
+}



More information about the llvm-commits mailing list