[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