@@crt0.asm and @@crt1.asm are 319 and 322 lines that differ in three, and the ecosystem has quietly settled on one of them. The variants cost more than they buy.
Measured
$ diff asm/@@crt0.asm asm/@@crt1.asm
The functional delta is one hunk:
L R1,=A(CTHREAD) A(thread driver routine) crt0: active
LA R0,=CL8'CTHREAD' crt1: commented out with ***
IDENTIFY EPLOC=(0),ENTRY=(1)
Everything else is WTO text ('@@CRT0 - No storage for C stack' vs '@@CRT1 - …'), a header comment, the word Get in one comment column — and one place where the two copies have already drifted: in @@CTEXIT, crt0 loads the return code (L R9,0(R1)) on entry, crt1 after the BALR to @@CRTGET.
That drift is harmless — @@CRTGET is a C function whose PDPEPIL ends RETURN (14,12),RC=(15), so R1 is restored — but it is an edit that landed in one copy and not the other. That is the whole argument for merging, in one line of diff.
What the ecosystem actually uses
132 startup = "crt1"
15 startup = false (own entry, no CRT)
1 startup = "crt0" (ind_file370)
0 startup = "crtm"
Nothing uses crtm, and essentially nothing uses crt0. The variant that is used everywhere is the one without the CTHREAD IDENTIFY.
Two questions to settle before merging, not after
1. What is the IDENTIFY actually for? Several project.toml files describe crt1 as "the threading runtime" (ftpd, httplua, httprexx), while @@crt1.asm's own header says "Copy of @@crt0 without the IDENTIFY for CTHREAD". The comments and the code disagree about which one is the threading variant. IDENTIFY makes CTHREAD a named entry point so ATTACH EP=CTHREAD can find it — so the question is how threads are attached today in the 132 modules that switched it off, and whether any path still needs it.
2. What is crtm for? Its intent is legible from the code, and it is not "smaller for the sake of smaller":
crt0/crt1 create the C environment for the task: GETMAIN the stack, create the CLIBCRT, then @@GRTSET to anchor a CLIBGRT.
crtm joins an environment that already exists: it GETMAINs only a stack and calls @@CRTGET — which merely looks up the CLIBCRT for the current TCB in the PPA array (src/clib/@@crtget.c), it never creates one — and abends U0801 '@@CRTM - No CLIBCRT (C environment missing)' if there is none.
So crtm is the startup for a program that gets control inside a task whose C runtime is already up — the LINK-into-the-server case. That is exactly the shape of an httpd CGI or display module, and those are linked with crt1, which creates a second environment on a TCB that already has one. Either crtm is obsolete and should go, or the CGI path is doing the environment setup twice and crtm is the module it should have been using. That question decides crtm's fate; it should not be answered by deleting it unread.
The merge itself
The mechanism is already in the same file, three lines above the stack GETMAIN:
WXTRN @@STKLEN
ICM R1,15,=V(@@STKLEN) Get stack length address
BZ USEDFLT not there -> default
Apply it to CTHREAD — WXTRN CTHREAD, ICM, skip the IDENTIFY when the address is zero — and crt0 and crt1 become one module. A weak external does not drive automatic library call, so a program that does not use threads never pulls the thread driver in; one that does references the thread API anyway, so the address is there and the IDENTIFY happens. No option, no variant, no decision for the caller to get wrong.
Related, and worth reading together: mvslovers/cc370#10 (startup customization by redefining @@START, whose agreed resolution is a weak __premain() hook — same mechanism, same file) and mvslovers/cc370#99 (ld370 mishandles a weak external when a hard ER for the same name exists — not a blocker for this change, since nothing declares a hard EXTRN CTHREAD, but it is the same mechanism and worth having correct first).
@@crt0.asmand@@crt1.asmare 319 and 322 lines that differ in three, and the ecosystem has quietly settled on one of them. The variants cost more than they buy.Measured
The functional delta is one hunk:
Everything else is
WTOtext ('@@CRT0 - No storage for C stack'vs'@@CRT1 - …'), a header comment, the word Get in one comment column — and one place where the two copies have already drifted: in@@CTEXIT, crt0 loads the return code (L R9,0(R1)) on entry, crt1 after theBALRto@@CRTGET.That drift is harmless —
@@CRTGETis a C function whosePDPEPILendsRETURN (14,12),RC=(15), so R1 is restored — but it is an edit that landed in one copy and not the other. That is the whole argument for merging, in one line of diff.What the ecosystem actually uses
Nothing uses
crtm, and essentially nothing usescrt0. The variant that is used everywhere is the one without the CTHREAD IDENTIFY.Two questions to settle before merging, not after
1. What is the IDENTIFY actually for? Several
project.tomlfiles describecrt1as "the threading runtime" (ftpd, httplua, httprexx), while@@crt1.asm's own header says "Copy of @@crt0 without the IDENTIFY for CTHREAD". The comments and the code disagree about which one is the threading variant.IDENTIFYmakesCTHREADa named entry point soATTACH EP=CTHREADcan find it — so the question is how threads are attached today in the 132 modules that switched it off, and whether any path still needs it.2. What is
crtmfor? Its intent is legible from the code, and it is not "smaller for the sake of smaller":crt0/crt1create the C environment for the task: GETMAIN the stack, create theCLIBCRT, then@@GRTSETto anchor aCLIBGRT.crtmjoins an environment that already exists: it GETMAINs only a stack and calls@@CRTGET— which merely looks up theCLIBCRTfor the current TCB in the PPA array (src/clib/@@crtget.c), it never creates one — and abendsU0801 '@@CRTM - No CLIBCRT (C environment missing)'if there is none.So
crtmis the startup for a program that gets control inside a task whose C runtime is already up — the LINK-into-the-server case. That is exactly the shape of an httpd CGI or display module, and those are linked with crt1, which creates a second environment on a TCB that already has one. Eithercrtmis obsolete and should go, or the CGI path is doing the environment setup twice andcrtmis the module it should have been using. That question decidescrtm's fate; it should not be answered by deleting it unread.The merge itself
The mechanism is already in the same file, three lines above the stack GETMAIN:
Apply it to
CTHREAD—WXTRN CTHREAD,ICM, skip theIDENTIFYwhen the address is zero — andcrt0andcrt1become one module. A weak external does not drive automatic library call, so a program that does not use threads never pulls the thread driver in; one that does references the thread API anyway, so the address is there and theIDENTIFYhappens. No option, no variant, no decision for the caller to get wrong.Related, and worth reading together:
mvslovers/cc370#10(startup customization by redefining@@START, whose agreed resolution is a weak__premain()hook — same mechanism, same file) andmvslovers/cc370#99(ld370 mishandles a weak external when a hard ER for the same name exists — not a blocker for this change, since nothing declares a hardEXTRN CTHREAD, but it is the same mechanism and worth having correct first).