fix.txt 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. CocoGm2 - CRS.mod Analysis and Fix
  2. ===================================
  3. Summary
  4. -------
  5. The file CRS.mod (a Modula-2 scanner generated by Coco/R) failed to compile
  6. with the GNU Modula-2 compiler (gm2). The compilation command was:
  7. gm2 -fiso -c CRS.mod
  8. The compiler crashed with an internal compiler error (ICE) instead of
  9. reporting a normal error/warning. This document records the analysis and
  10. the fixes applied.
  11. ----------------------------------------------------------------------
  12. 1. Original symptoms
  13. ----------------------------------------------------------------------
  14. The raw compile of CRS.mod produced:
  15. cc1gm2: internal compiler error: ProcedureSym kind has not yet
  16. been declared
  17. <backtrace ending in>
  18. ...SymbolTable_GetProcedureKind
  19. ...P2SymBuild_BuildFPSection
  20. ...FormalParameters -> ... -> ProcedureHeading -> ... -> Declaration
  21. FileIO.mod compiled fine with the same flags, so the problem was specific
  22. to CRS.mod, not to the toolchain in general.
  23. ----------------------------------------------------------------------
  24. 2. Initial findings during review
  25. ----------------------------------------------------------------------
  26. Several suspicious items were noted during the code review:
  27. (a) Forward reference
  28. GetString() called the function CharAt() which was declared LATER in
  29. the file. In Modula-2 a procedure must be declared before it is used.
  30. (b) Unused local ORDL wrapper
  31. A module-level procedure `ORDL(n: INT32): CARDINAL` wrapped
  32. FileIO.ORDL(), but the rest of the code called FileIO.ORDL() directly,
  33. so the wrapper was dead code.
  34. (c) Nested procedures inside Get()
  35. Get() contained two nested procedures:
  36. - Equal(s: ARRAY OF CHAR): BOOLEAN
  37. - CheckLiteral
  38. Nested procedures with open-array (ARRAY OF CHAR) parameters were a
  39. suspected compiler-bug trigger.
  40. (d) CurrentCh procedure-type variable
  41. CurrentCh was declared as type GetCH = PROCEDURE (INT32): CHAR and was
  42. (re)assigned to CharAt() in the module initialization block.
  43. ----------------------------------------------------------------------
  44. 3. Isolation (minimization)
  45. ----------------------------------------------------------------------
  46. The ICE was reproduced and reduced to a minimal case:
  47. IMPLEMENTATION MODULE CRS2;
  48. IMPORT FileIO;
  49. ...
  50. PROCEDURE ORDL (n: INT32): CARDINAL;
  51. BEGIN
  52. RETURN FileIO.ORDL(n)
  53. END ORDL;
  54. ...
  55. Results of the reductions:
  56. - Removing Err() -> still crashed
  57. - Removing NextCh() -> still crashed
  58. - Keeping ONLY the ORDL procedure -> STILL crashed
  59. - Renaming "ORDL" to "ORDLX" -> compiled cleanly
  60. CONCLUSION: the crash was caused by DECLARING a module-level procedure
  61. with the SAME NAME as an imported procedure from FileIO
  62. (i.e. `ORDL` shadowing `FileIO.ORDL`). gm2 trips over the name clash and
  63. hits "ProcedureSym kind has not yet been declared" while building the
  64. formal parameter section of surrounding/affected procedures.
  65. The earlier hypothesis (nested procedures, procedure-type variables,
  66. open-array parameters) was explored but is NOT the cause of the ICE; those
  67. constructs compiled fine in isolation.
  68. ----------------------------------------------------------------------
  69. 4. Fixes applied to CRS.mod
  70. ----------------------------------------------------------------------
  71. (1) Removed the unused module-level `ORDL` wrapper procedure. This is the
  72. actual fix for the compiler crash. All remaining references already
  73. use the fully-qualified FileIO.ORDL(), which is correct.
  74. (2) Restored `CurrentCh: GetCH;` in the VAR section (it had been lost
  75. from the file during earlier save/stash actions). CurrentCh is used
  76. by NextCh(), Equal() and CheckLiteral().
  77. (3) Moved CharAt() and CapChAt() BEFORE GetString()/GetName() to resolve
  78. the forward reference of CharAt() from GetString().
  79. (4) Extracted the nested procedures Equal() and CheckLiteral() out of
  80. Get() to module level for clarity and to keep away from gm2's
  81. nested-procedure handling. CheckLiteral now takes sym as a VAR
  82. parameter and the call site was updated to CheckLiteral(sym).
  83. ----------------------------------------------------------------------
  84. 5. Verification
  85. ----------------------------------------------------------------------
  86. After the fixes the module compiles cleanly:
  87. gm2 -fiso -c CRS.mod (no errors, no warnings)
  88. Note: the git diff for CRS.mod appears large because the original file
  89. used CRLF line endings while the edited file uses LF; the real functional
  90. change is the four items listed in section 4.
  91. ----------------------------------------------------------------------
  92. 6. Whole-project build (./build.sh)
  93. ----------------------------------------------------------------------
  94. After CRS.mod compiled, ./build.sh was run. Two further problems were
  95. found and fixed. Both were exposed only during the final link step, in
  96. which gm2 links with the scaffold flags "-fscaffold-dynamic
  97. -fscaffold-main" and re-checks the imported implementation modules.
  98. 6.1 gm2 false "s already declared" error in CRA.mod (MatchDFA)
  99. During the link step gm2 reported:
  100. cc1gm2: error: In procedure « MatchDFA »: symbol « s » is
  101. already declared in this scope, use a different name or remove
  102. the declaration
  103. ./CRA.mod:659:7: error: symbol « s » also declared in this module
  104. 659 | s, to: INTEGER (* State *);
  105. Analysis:
  106. - CRA.mod compiles fine standalone (gm2 -fiso -c CRA.mod).
  107. - CR.mod compiles fine standalone (gm2 -fiso -c CR.mod).
  108. - The error appears ONLY when compiling CR.mod with the scaffold
  109. flags (as gm2 does during linking):
  110. gm2 -fiso -fscaffold-dynamic -fscaffold-main -c CR.mod
  111. - The scaffolding re-parses the imported implementation module
  112. CRA.mod, and a procedure-local variable "s" from some earlier
  113. scope leaks into MatchDFA's scope, so the local declaration of
  114. "s" is flagged as a duplicate. This is a gm2 scope-tracking bug,
  115. not a real error in the source.
  116. - Renaming the local "s" in procedures such as TheState() did NOT
  117. help; renaming the "s" inside MatchDFA() itself does.
  118. Fix applied to CRA.mod:
  119. Renamed MatchDFA()'s local variable "s" to "st" (declaration and
  120. all 7 uses inside the procedure). This avoids the collision with
  121. the leaked symbol while keeping the code identical in behaviour.
  122. 6.2 build.sh errors in the module list
  123. Once compilation and linking succeeded past the CRA error, the link
  124. failed twice more because build.sh referenced module object files
  125. that were either never built or not needed:
  126. - "CRQ.o" was in the link line but is never compiled. CRQ.mod is a
  127. legacy alternate MAIN module (not CR); CR.mod does not import
  128. CRQ. -> removed CRQ.o from the link line.
  129. - "CRX.o" was built by the script but was missing from the link
  130. line, yet CR.mod calls CRX.GenCompiler and CRX.WriteStatistics.
  131. -> added CRX.o to the link line.
  132. Final link command in build.sh:
  133. gm2 -fiso -o CR CRA.o CRC.o CRG.o CRP.o CRS.o CRT.o CRX.o \
  134. FileIO.o Sets.o CR.mod
  135. ----------------------------------------------------------------------
  136. 7. Final verification
  137. ----------------------------------------------------------------------
  138. ./build.sh
  139. Building Coc/R with GNU Modula-2
  140. Deleting all o files
  141. compiling the needed modules
  142. +++++++Modules compiled ++++++++++++++++++++++
  143. Compiling main module and linking all together
  144. (exit code 0)
  145. Produces the executable ./CR.
  146. Smoke test:
  147. echo "?" | ./CR
  148. Coco/R (WinTel) - Compiler-Compiler V1.53
  149. Released by Pat Terry 17 September 2002
  150. (COCOR ? gives short help screen)
  151. Grammar[.atg] ? : File <?.atg> not found.
  152. Grammar[.atg] ? :
  153. The executable starts and behaves as expected.
  154. ----------------------------------------------------------------------
  155. Files changed (this session)
  156. ----------------------------------------------------------------------
  157. CRS.mod - removed unused ORDL wrapper; restored CurrentCh; moved
  158. CharAt/CapChAt before GetString/GetName; extracted
  159. Equal/CheckLiteral to module level.
  160. CRA.mod - renamed MatchDFA local variable s -> st.
  161. build.sh - fixed module list for the link step (drop CRQ.o,
  162. add CRX.o).
  163. fix.txt - this file.
  164. ----------------------------------------------------------------------
  165. 8. IsoFrames adapted to FileIO (ISO build works fully, 2026)
  166. ----------------------------------------------------------------------
  167. Goal: make the ISO-flavoured frames (IsoFrames/) regenerate CRS, CRP and
  168. the driver such that the whole build compiles and LINKS with `gm2 -fiso`.
  169. Why it broke before:
  170. - iso scanner frame emitted `src, lst: IOChan.ChanId` in its DEFINITION
  171. MODULE, but the support modules CRA/CRT/CRX call `FileIO.WriteString
  172. (CRS.lst, ...)`, which requires `CRS.lst: FileIO.File`. The types are
  173. incompatible (FileIO.File is a POINTER TO FileRec wrapping its own
  174. hidden ChanId).
  175. - iso parser frame emitted only `IMPORT <Scanner>;`; the grammar's
  176. semantic actions (CR.atg) use FileIO.INT32/Long0/Long2/INTL/INT, so the
  177. generated parser missed the FileIO import.
  178. - iso compiler/compile2/gpm frames used `Storage.ALLOCATE(nextErr,
  179. SYSTEM.TSIZE(ErrDesc))` INSIDE a NESTED module (ListHandler). In ISO
  180. mode gm2 rejects that: "Storage looks like a module which has not been
  181. globally imported". The working pattern (proved with testS3.mod) is to
  182. import inside the nested module:
  183. FROM Storage IMPORT ALLOCATE;
  184. FROM SYSTEM IMPORT TSIZE;
  185. and then call ALLOCATE(p, TSIZE(T)) unqualified.
  186. Changes to IsoFrames:
  187. - scanner.frm / scanner2.frm / scannerc.frm
  188. * DEFINITION: `IMPORT FileIO;` + `INT32 = FileIO.INT32;` +
  189. `src, lst: FileIO.File;`
  190. * IMPLEMENTATION: `IMPORT FileIO, Storage;` (scannerc also keeps
  191. SYSTEM for TSIZE); Reset reads via FileIO.ReadBytes instead of
  192. IOChan.RawRead:
  193. read := BlkSize; FileIO.ReadBytes(src, buf[i]^, read);
  194. * scanner2 uses FileIO.err + FileIO.ReadBytes in its single-buffer
  195. Reset.
  196. - parser.frm: `IMPORT -->scanner;` -> `IMPORT -->scanner, FileIO;` so the
  197. generated parser gets FileIO as well as the scanner's range (matches the
  198. classic frame behaviour `IMPORT FileIO, CRS;`).
  199. - compiler.frm / compile2.frm / compiler.gpm (rudimentary drivers):
  200. * ListHandler nested module now does
  201. FROM FileIO IMPORT CR, LF, EOF, WriteString, Write, WriteLn,
  202. WriteInt, Long0;
  203. FROM Storage IMPORT ALLOCATE;
  204. FROM SYSTEM IMPORT TSIZE;
  205. and calls ALLOCATE/TSIZE unqualified; the old CONST CR/LF/EOF have
  206. been removed (they come from FileIO now).
  207. * main module switched from ProgramArgs/StdChans/SeqFile/TextIO/
  208. WholeIO to FileIO: FileIO.NextParameter, FileIO.Open(src,...,FALSE),
  209. FileIO.Open(lst,...,TRUE), FileIO.StdOut/FileIO.err,
  210. FileIO.WriteString/WriteInt/WriteLn, FileIO.Close.
  211. NOTE: FileIO.NextParameter is a PROCEDURE with no return value, so
  212. it must NOT be used in `IF FileIO.NextParameter(...)`; pass a local
  213. buffer and test `IF buf[0] = 0C`.
  214. Critical gotcha found while linking:
  215. - gm2 fails during whole-program checking (pass 3) with
  216. CRP.mod: error: In procedure <TokenFactor>: too many errors in pass 3
  217. whenever the generated parser contains ACTIVE `...FORWARD;` declarations.
  218. The classic build avoids this by generating the parser with the
  219. multipass option: CR -m CR.atg
  220. which makes GenForwardRefs (CRX.mod:539) wrap the FORWARD block in
  221. (* ----- FORWARD not needed in multipass compilers ... ----- *)
  222. - Symptom was independent of directory/timestamps; only commenting the
  223. FORWARD block out fixed it.
  224. Verified full ISO build (fresh dir, gm2 -fiso):
  225. CRFRAMES=<repo>/IsoFrames <repo>/CR -m CR.atg
  226. # compiles: CRA CRC CRG CRT CRX CRS CRP FileIO Sets -> all EXIT 0
  227. gm2 -fiso -o coco CRA.o CRC.o CRG.o CRT.o CRX.o CRS.o CRP.o \
  228. FileIO.o Sets.o CR.mod
  229. # link OK; ./coco CR.atg -> "Parsed correctly"
  230. Support modules CRA/CRT/CRX remained unchanged (FileIO already).
  231. ----------------------------------------------------------------------
  232. 9. Pascal build from pascal.atg + gm2 whole-program link bug (2026)
  233. ----------------------------------------------------------------------
  234. Goal: generate a Pascal compiler from pascal.atg using the same
  235. FileIO-based frames and link it with gm2 -fiso.
  236. Generation command (CRFRAMES points to directory containing the frames):
  237. CRFRAMES=<frames-dir> CR -m -C pascal.atg
  238. - -m suppresses FORWARD declarations (multipass-safe code)
  239. - -C generates the driver (Pascal.mod from compiler.frm)
  240. Without -C, only PascalS.mod and PascalP.mod are generated;
  241. Pascal.mod is NOT written (gated on CRT.ddt["C"] at CR.mod:463-466).
  242. The grammar is intentionally not LL(1) so CR always ends with
  243. "Compilation ended with LL(1) errors." — this is expected and harmless;
  244. PascalS/PascalP/Pascal are still written.
  245. Compilation:
  246. for m in FileIO PascalS PascalP Pascal; do
  247. gm2 -fiso -c $m.mod
  248. done
  249. All four modules compile cleanly (EXIT 0).
  250. Link problem:
  251. gm2 -fiso -o Pascal PascalS.o PascalP.o FileIO.o Pascal.mod
  252. fails with:
  253. RndFile.def:27: error: In definition module RndFile: the constructor
  254. type is FlagSet and this is different from the constant constant set
  255. which has a type FlagSet
  256. (same for TermFile.def:31, ChanConsts.def:33)
  257. Root cause:
  258. gm2's default link step performs whole-program pass-3 analysis:
  259. it re-parses ALL imported definition modules (including its own
  260. bundled m2iso: RndFile.def, TermFile.def, ChanConsts.def) and
  261. type-checks every constant set-constructor. When the total number
  262. of identifiers/symbols in the program exceeds an internal threshold,
  263. a buffer overflow corrupts the type of FlagSet inside gm2 itself,
  264. producing the spurious "constructor type is FlagSet" error on the
  265. m2iso library's own code.
  266. This is the same class of bug reported on the gm2 mailing list
  267. (2023-05, msg00039/msg00045, by Michael Riedl, with workaround
  268. explained by Gaius Mulley).
  269. Trigger is symbol-count threshold, not a specific procedure:
  270. adding any sufficiently large dummy procedure to a working module
  271. causes the same failure; removing any ~50 identifiers from the
  272. failing module drops below threshold and restores success.
  273. Workaround — two-step link using module list:
  274. gm2 -fiso -fgen-module-list=modules.lst \
  275. -o /dev/null PascalS.o PascalP.o FileIO.o Pascal.mod
  276. gm2 -fiso -fuse-list=modules.lst \
  277. -o Pascal PascalS.o PascalP.o FileIO.o Pascal.mod
  278. The first invocation generates the module list (despite printing
  279. the RndFile error; the list is still written and the error is
  280. non-fatal). The second uses the cached list and AVOIDS the
  281. whole-program re-analysis entirely — link succeeds, binary works.
  282. Verified full end-to-end build (fresh dir):
  283. cp <repo>/Test/{FileIO.mod,FileIO.def,compiler.frm,parser.frm,
  284. scanner.frm,compiler.gpm,pascal.atg} <build-dir>/
  285. CRFRAMES=. CR -m -C pascal.atg
  286. for m in FileIO PascalS PascalP Pascal; do gm2 -fiso -c $m.mod; done
  287. gm2 -fiso -fgen-module-list=modules.lst -o /dev/null \
  288. PascalS.o PascalP.o FileIO.o Pascal.mod
  289. gm2 -fiso -fuse-list=modules.lst -o Pascal \
  290. PascalS.o PascalP.o FileIO.o Pascal.mod
  291. # ./Pascal /tmp/test.pas -> "Parsing Parsed correctly" (exit 0)