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

via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 26 08:37:48 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-systemz

Author: MMS IT GmbH (mms-it-ch)

<details>
<summary>Changes</summary>

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)


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


4 Files Affected:

- (modified) llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.cpp (+52) 
- (modified) llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.h (+1) 
- (added) llvm/test/CodeGen/SystemZ/zos-celqmain-nomain.ll (+13) 
- (added) llvm/test/CodeGen/SystemZ/zos-celqmain.ll (+30) 


``````````diff
diff --git a/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.cpp b/llvm/lib/Target/SystemZ/SystemZXPLINKAsmPrinter.cpp
index 3ab3dfc4a0bcb..5f8c2de88c2a4 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 b4deeb4fc262c..c221c02faf36c 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 0000000000000..c6cdc5b67c066
--- /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 0000000000000..04b41409dc6eb
--- /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
+}

``````````

</details>


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


More information about the llvm-commits mailing list