markdown · 11802 bytes Raw Blame History

armfortas

A Fortran compiler for ARM64. No borrowed frontends, no LLVM, no GCC. Every stage from lexing to machine code is ours.

Why

fortsh is a ~57,000-line Fortran 2018 shell. When we went to compile it on Apple Silicon we found out that Fortran on ARM64 is, charitably, underserved:

  • gfortran has at least seven confirmed critical bugs on macOS ARM64, most of them involving allocatable strings — the exact feature fortsh leans on hardest. The bugs live in GCC's backend and the GCC team's queue for ARM64 Fortran is not short.
  • flang-new (LLVM) works around the gfortran bugs but introduces its own, particularly around C interop and derived type layout. It also requires a separate Homebrew installation and the binary is called flang-new for reasons.
  • Both compilers are millions of lines of code we don't own. When something breaks in a corner of AArch64 AAPCS64 that nobody expected a Fortran program to reach, "read the source and fix it" is not a realistic option.

The solution was to write a compiler that runs on the machine we actually have, that we can fix when it breaks.

Status

Active development. The full pipeline — preprocessor through Mach-O object emission — is working. Real Fortran programs compile and run.

Pipeline: Source → Preprocessor → Lexer → Parser → AST →
          Sema → SSA IR → Optimizations → ARM64 Codegen →
          afs-as → Mach-O .o → ld → Binary

The system linker (ld) is the only component we delegate. Everything else is ours.

Build

git clone --recurse-submodules https://github.com/FortranGoingOnForty/armfortas.git
cd armfortas
cargo build --workspace          # compiler + assembler + runtime
cargo test --workspace           # full test suite
cargo clippy --workspace         # lint

Once built:

target/debug/armfortas hello.f90 -o hello    # compile and link
target/debug/armfortas -c module.f90         # compile to object
target/debug/armfortas -S hello.f90          # emit assembly
target/debug/armfortas --emit-ir hello.f90   # emit IR

What Works

Language coverage (F77 through F2018)

  • Free-form and fixed-form source
  • All numeric types: integer, real, double precision, logical, character
  • Complex arithmetic (storage and arithmetic operations; some intrinsics pending)
  • Derived types with component access, type extension (EXTENDS), and type-bound procedures with PASS/NOPASS
  • FINAL procedures
  • SELECT TYPE with TYPE IS and CLASS IS guards
  • ALLOCATABLE scalars and arrays, including allocatable character strings
  • POINTER and TARGET attributes
  • OPTIONAL arguments with PRESENT() intrinsic
  • Full array sections and whole-array expressions
  • WHERE / FORALL constructs
  • DO, DO WHILE, DO CONCURRENT with locality specs
  • SELECT CASE on integer, character, and logical
  • ASSOCIATE and BLOCK constructs
  • GOTO and labeled statements
  • EQUIVALENCE and COMMON blocks
  • NAMELIST I/O
  • SAVE attribute with correct static storage
  • VALUE attribute for pass-by-value (BIND(C))
  • RECURSIVE functions and subroutines
  • Generic procedures and interfaces
  • Operator overloading
  • Statement functions
  • Arithmetic IF
  • STOP / ERROR STOP with stop codes

C interoperability (iso_c_binding)

Full iso_c_binding module: kind parameters (C_INT, C_DOUBLE, C_CHAR, etc.), C_PTR, C_NULL_PTR, C_LOC, C_FUNPTR, BIND(C) procedures with correct ABI including VALUE argument dispatch.

I/O

  • PRINT and WRITE with format strings and list-directed I/O
  • READ from stdin and files
  • OPEN, CLOSE, INQUIRE, REWIND, BACKSPACE, ENDFILE, FLUSH
  • Unformatted (binary) I/O
  • Stream I/O
  • Non-advancing I/O
  • FORMAT statements
  • NAMELIST groups

Intrinsics

Mathematical: ABS, SQRT, EXP, LOG, LOG10, SIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2, SINH, COSH, TANH, MOD, MODULO, SIGN, DIM, FLOOR, CEILING, NINT, INT, REAL, DBLE, MAX, MIN, MAXVAL, MINVAL, SUM, PRODUCT

Array: SIZE, SHAPE, LBOUND, UBOUND, ALLOCATED, ASSOCIATED, RESHAPE, TRANSPOSE, MATMUL, DOT_PRODUCT, PACK, UNPACK, SPREAD, MERGE, COUNT, ANY, ALL

Character: LEN, LEN_TRIM, TRIM, ADJUSTL, ADJUSTR, INDEX, SCAN, VERIFY, CHAR, ICHAR, ACHAR, IACHAR, REPEAT, NEW_LINE

Bit: IAND, IOR, IEOR, NOT, ISHFT, ISHFTC, IBITS, IBSET, IBCLR, BTEST, POPCNT, POPPAR, LEADZ, TRAILZ

System: SYSTEM_CLOCK, CPU_TIME, DATE_AND_TIME, RANDOM_NUMBER, RANDOM_SEED, COMMAND_ARGUMENT_COUNT, GET_COMMAND_ARGUMENT, GET_COMMAND, GET_ENVIRONMENT_VARIABLE

Inquiry: KIND, SELECTED_INT_KIND, SELECTED_REAL_KIND, HUGE, TINY, EPSILON, PRECISION, RANGE, DIGITS, RADIX, MINEXPONENT, MAXEXPONENT

Optimization passes

Level Passes
-O0 None (preserve IR exactly)
-O1 mem2reg, constant folding, local CSE, constant propagation, DCE
-O2 -O1 + strength reduction, LICM
-O3 Same as -O2 (vectorization and IPO deferred — see below)
-Ofast -O3 + fast-math reassociation for float add/sub constant chains

Correctness invariant: every program that produces correct output at -O0 must produce identical output at -O1, -O2, -O3. This is enforced by the end-to-end test suite at every level.

Modules

iso_c_binding and iso_fortran_env are built-in and always available. Authored modules compile correctly. Multi-file module dependency resolution and .amod files are in progress (Sprint 30).

What Doesn't Work Yet

In progress or deferred:

  • Complex number intrinsics (REAL(), AIMAG(), CONJG(), CMPLX()) — storage works, intrinsic calls don't
  • Stack frames larger than ~32KB — prologue/epilogue currently broken above that threshold; practically affects very large local arrays
  • -O3 vectorization (NEON/SIMD) — accepted and correct but runs -O2 passes
  • Function inlining — not yet implemented at any level
  • Multi-file compilation and .amod module files — Sprint 30
  • Loop unrolling, GVN, SROA, dead store elimination — later optimizer sprints
  • ieee_arithmetic, ieee_exceptions modules
  • Coarray Fortran
  • Submodules
  • Vtable-based polymorphic dispatch through CLASS variables

Architecture

armfortas/
├── afs-as/          Standalone ARM64 assembler (git submodule)
│   └── src/         Instruction encoding, .s parser, Mach-O emission
├── src/
│   ├── preprocess/  Fortran-aware preprocessor (#ifdef, #include, #define)
│   ├── lexer/       Tokenization — free-form + fixed-form
│   ├── parser/      Recursive descent → AST
│   ├── ast/         AST node definitions
│   ├── sema/        Symbol tables, type system, validation
│   ├── ir/          SSA-form IR with block parameters (no phi nodes)
│   ├── opt/         Optimization passes and pass manager
│   ├── codegen/     ARM64 instruction selection, linear scan register allocation
│   ├── driver/      CLI, compilation orchestration
│   └── runtime/     libarmfortas_rt — I/O, intrinsics, memory management
├── bencch/          Compiler benchmark and test harness (git submodule)
├── test_programs/   ~110 end-to-end test programs with CHECK annotations
└── runtime/         Runtime library source

Key design decisions

No LLVM. gfortran's bugs are in GCC's backend. flang's bugs are in LLVM's frontend lowering. Using either as a backend would mean inheriting the bugs we're trying to escape. We own every pass.

SSA IR with block parameters. Instead of phi nodes, blocks carry typed parameters. Cleaner to construct, easier to verify, simpler to transform. The mem2reg pass promotes stack allocas to SSA values using iterated dominance frontiers (Cytron et al.).

Apple AAPCS64 strictly. 16-byte stack alignment always, x18 reserved, x29/x30 saved in prologue, frame pointer maintained. We've been bitten by every one of these constraints and handle them correctly.

Array descriptors. {base_addr, elem_size, rank, flags, dims[15]}. Our ABI — stable across releases.

String descriptors. {data, len, capacity, flags}. Deferred-length assignment always allocates new storage before freeing old. This prevents the use-after-free that causes gfortran's ARM64 allocatable string crashes.

Large arrays on heap. Stack threshold at 64KB. Prevents the stack corruption gfortran exhibits with arrays over ~600KB.

afs-as is the standalone ARM64 assembler. It knows nothing about Fortran — clean API boundary. It can be used independently to assemble ARM64 .s files.

Testing

cargo test --workspace                            # all unit + integration tests
cargo test --test run_programs                    # end-to-end at -O0
cargo test --test run_programs -- --nocapture     # verbose output
cargo run -p afs-tests -- run --suite runtime     # bencch runtime suite
cargo run -p afs-tests -- run --suite consistency # reproducibility checks

The root armfortas harness is the fast, armfortas-first runner. It compiles each .f90 file in test_programs/, runs the binary, and evaluates source-embedded assertions such as:

  • ! CHECK: for stdout
  • ! STDERR_CHECK: for runtime stderr
  • ! EXIT_CODE: for exact runtime exit status
  • ! XFAIL: for known open bugs
  • ! ERROR_EXPECTED: for diagnostics that must be emitted
  • ! ERROR_SPAN: for exact diagnostic location
  • ! ASM_CHECK: / ! ASM_NOT: for assembly shape
  • ! FILE_CHECK: / ! FILE_NOT: for sandbox file side effects
  • ! FILE_EXISTS: / ! FILE_MISSING: for explicit sandbox presence or absence
  • ! FILE_LINE_COUNT: for structural file-shape assertions
  • ! FILE_RERUN_MODE: for explicit overwrite vs append intent across reruns
  • ! FILE_SET_EXACT: for exact runtime side-effect file sets
  • ! REPRO_CHECK: for per-test asm/object/run reproducibility
  • ! OPT_EQ: for explicit cross-opt invariants
  • ! PHASE_TRIANGULATE: for same-opt IR/ASM/object availability, compile-cleanliness, and compile-only reproducibility oracles
  • ! IR_CHECK: / ! IR_NOT: for IR shape

Those source comments are the canonical leaf-assertion language for the project. The root harness is where new annotation ideas should land first.

bencch is the structured matrix/reporting/differential runner around that same testing language. It is best for:

  • opt matrices
  • differential/reference runs
  • module graphs
  • capability-aware execution
  • reports and bundles

The two surfaces are meant to converge on syntax and expectations, not drift into separate testing dialects.

All root end-to-end tests run at every optimization level (-O0 through -Ofast). Programs with known bugs carry ! XFAIL: annotations that reference the audit finding — they count as passing until the bug is fixed, at which point CI catches the unexpected success.

Target

  • Architecture: ARM64 (AArch64), Apple Silicon (M1/M2/M3/M4)
  • OS: macOS (Mach-O, Apple AAPCS64)
  • Standard: F77 through F2018, building inward from F2018
  • Goal: Compile fortsh — a 57,000-line Fortran 2018 shell — correctly on Apple Silicon

Compiling fortsh is a milestone, not the finish line. A complete compiler handles code fortsh never exercises.

Relationship to fortsh

This compiler exists because fortsh exists. Building a non-trivial Fortran program on ARM64 and discovering that neither available compiler handles it reliably was the motivation. The goal is a compiler we can fix when it breaks, running on the hardware we actually use.

View source
1 # armfortas
2
3 A Fortran compiler for ARM64. No borrowed frontends, no LLVM, no GCC. Every stage from lexing to machine code is ours.
4
5 ## Why
6
7 [fortsh](https://github.com/FortranGoingOnForty/fortsh) is a ~57,000-line Fortran 2018 shell. When we went to compile it on Apple Silicon we found out that Fortran on ARM64 is, charitably, underserved:
8
9 - **gfortran** has at least seven confirmed critical bugs on macOS ARM64, most of them involving allocatable strings — the exact feature fortsh leans on hardest. The bugs live in GCC's backend and the GCC team's queue for ARM64 Fortran is not short.
10 - **flang-new (LLVM)** works around the gfortran bugs but introduces its own, particularly around C interop and derived type layout. It also requires a separate Homebrew installation and the binary is called `flang-new` for reasons.
11 - Both compilers are millions of lines of code we don't own. When something breaks in a corner of AArch64 AAPCS64 that nobody expected a Fortran program to reach, "read the source and fix it" is not a realistic option.
12
13 The solution was to write a compiler that runs on the machine we actually have, that we can fix when it breaks.
14
15 ## Status
16
17 Active development. The full pipeline — preprocessor through Mach-O object emission — is working. Real Fortran programs compile and run.
18
19 ```
20 Pipeline: Source → Preprocessor → Lexer → Parser → AST →
21 Sema → SSA IR → Optimizations → ARM64 Codegen →
22 afs-as → Mach-O .o → ld → Binary
23 ```
24
25 The system linker (`ld`) is the only component we delegate. Everything else is ours.
26
27 ## Build
28
29 ```bash
30 git clone --recurse-submodules https://github.com/FortranGoingOnForty/armfortas.git
31 cd armfortas
32 cargo build --workspace # compiler + assembler + runtime
33 cargo test --workspace # full test suite
34 cargo clippy --workspace # lint
35 ```
36
37 Once built:
38
39 ```bash
40 target/debug/armfortas hello.f90 -o hello # compile and link
41 target/debug/armfortas -c module.f90 # compile to object
42 target/debug/armfortas -S hello.f90 # emit assembly
43 target/debug/armfortas --emit-ir hello.f90 # emit IR
44 ```
45
46 ## What Works
47
48 ### Language coverage (F77 through F2018)
49
50 - Free-form and fixed-form source
51 - All numeric types: `integer`, `real`, `double precision`, `logical`, `character`
52 - Complex arithmetic (storage and arithmetic operations; some intrinsics pending)
53 - Derived types with component access, type extension (`EXTENDS`), and type-bound procedures with `PASS`/`NOPASS`
54 - `FINAL` procedures
55 - `SELECT TYPE` with `TYPE IS` and `CLASS IS` guards
56 - `ALLOCATABLE` scalars and arrays, including allocatable character strings
57 - `POINTER` and `TARGET` attributes
58 - `OPTIONAL` arguments with `PRESENT()` intrinsic
59 - Full array sections and whole-array expressions
60 - `WHERE` / `FORALL` constructs
61 - `DO`, `DO WHILE`, `DO CONCURRENT` with locality specs
62 - `SELECT CASE` on integer, character, and logical
63 - `ASSOCIATE` and `BLOCK` constructs
64 - `GOTO` and labeled statements
65 - `EQUIVALENCE` and `COMMON` blocks
66 - `NAMELIST` I/O
67 - `SAVE` attribute with correct static storage
68 - `VALUE` attribute for pass-by-value (`BIND(C)`)
69 - `RECURSIVE` functions and subroutines
70 - Generic procedures and interfaces
71 - Operator overloading
72 - Statement functions
73 - Arithmetic IF
74 - `STOP` / `ERROR STOP` with stop codes
75
76 ### C interoperability (`iso_c_binding`)
77
78 Full `iso_c_binding` module: kind parameters (`C_INT`, `C_DOUBLE`, `C_CHAR`, etc.), `C_PTR`, `C_NULL_PTR`, `C_LOC`, `C_FUNPTR`, `BIND(C)` procedures with correct ABI including `VALUE` argument dispatch.
79
80 ### I/O
81
82 - `PRINT` and `WRITE` with format strings and list-directed I/O
83 - `READ` from stdin and files
84 - `OPEN`, `CLOSE`, `INQUIRE`, `REWIND`, `BACKSPACE`, `ENDFILE`, `FLUSH`
85 - Unformatted (binary) I/O
86 - Stream I/O
87 - Non-advancing I/O
88 - `FORMAT` statements
89 - `NAMELIST` groups
90
91 ### Intrinsics
92
93 Mathematical: `ABS`, `SQRT`, `EXP`, `LOG`, `LOG10`, `SIN`, `COS`, `TAN`, `ASIN`, `ACOS`, `ATAN`, `ATAN2`, `SINH`, `COSH`, `TANH`, `MOD`, `MODULO`, `SIGN`, `DIM`, `FLOOR`, `CEILING`, `NINT`, `INT`, `REAL`, `DBLE`, `MAX`, `MIN`, `MAXVAL`, `MINVAL`, `SUM`, `PRODUCT`
94
95 Array: `SIZE`, `SHAPE`, `LBOUND`, `UBOUND`, `ALLOCATED`, `ASSOCIATED`, `RESHAPE`, `TRANSPOSE`, `MATMUL`, `DOT_PRODUCT`, `PACK`, `UNPACK`, `SPREAD`, `MERGE`, `COUNT`, `ANY`, `ALL`
96
97 Character: `LEN`, `LEN_TRIM`, `TRIM`, `ADJUSTL`, `ADJUSTR`, `INDEX`, `SCAN`, `VERIFY`, `CHAR`, `ICHAR`, `ACHAR`, `IACHAR`, `REPEAT`, `NEW_LINE`
98
99 Bit: `IAND`, `IOR`, `IEOR`, `NOT`, `ISHFT`, `ISHFTC`, `IBITS`, `IBSET`, `IBCLR`, `BTEST`, `POPCNT`, `POPPAR`, `LEADZ`, `TRAILZ`
100
101 System: `SYSTEM_CLOCK`, `CPU_TIME`, `DATE_AND_TIME`, `RANDOM_NUMBER`, `RANDOM_SEED`, `COMMAND_ARGUMENT_COUNT`, `GET_COMMAND_ARGUMENT`, `GET_COMMAND`, `GET_ENVIRONMENT_VARIABLE`
102
103 Inquiry: `KIND`, `SELECTED_INT_KIND`, `SELECTED_REAL_KIND`, `HUGE`, `TINY`, `EPSILON`, `PRECISION`, `RANGE`, `DIGITS`, `RADIX`, `MINEXPONENT`, `MAXEXPONENT`
104
105 ### Optimization passes
106
107 | Level | Passes |
108 |-------|--------|
109 | `-O0` | None (preserve IR exactly) |
110 | `-O1` | mem2reg, constant folding, local CSE, constant propagation, DCE |
111 | `-O2` | `-O1` + strength reduction, LICM |
112 | `-O3` | Same as `-O2` (vectorization and IPO deferred — see below) |
113 | `-Ofast` | `-O3` + fast-math reassociation for float add/sub constant chains |
114
115 Correctness invariant: every program that produces correct output at `-O0` must produce identical output at `-O1`, `-O2`, `-O3`. This is enforced by the end-to-end test suite at every level.
116
117 ### Modules
118
119 `iso_c_binding` and `iso_fortran_env` are built-in and always available. Authored modules compile correctly. Multi-file module dependency resolution and `.amod` files are in progress (Sprint 30).
120
121 ## What Doesn't Work Yet
122
123 **In progress or deferred:**
124
125 - Complex number intrinsics (`REAL()`, `AIMAG()`, `CONJG()`, `CMPLX()`) — storage works, intrinsic calls don't
126 - Stack frames larger than ~32KB — prologue/epilogue currently broken above that threshold; practically affects very large local arrays
127 - `-O3` vectorization (NEON/SIMD) — accepted and correct but runs `-O2` passes
128 - Function inlining — not yet implemented at any level
129 - Multi-file compilation and `.amod` module files — Sprint 30
130 - Loop unrolling, GVN, SROA, dead store elimination — later optimizer sprints
131 - `ieee_arithmetic`, `ieee_exceptions` modules
132 - Coarray Fortran
133 - Submodules
134 - Vtable-based polymorphic dispatch through `CLASS` variables
135
136 ## Architecture
137
138 ```
139 armfortas/
140 ├── afs-as/ Standalone ARM64 assembler (git submodule)
141 │ └── src/ Instruction encoding, .s parser, Mach-O emission
142 ├── src/
143 │ ├── preprocess/ Fortran-aware preprocessor (#ifdef, #include, #define)
144 │ ├── lexer/ Tokenization — free-form + fixed-form
145 │ ├── parser/ Recursive descent → AST
146 │ ├── ast/ AST node definitions
147 │ ├── sema/ Symbol tables, type system, validation
148 │ ├── ir/ SSA-form IR with block parameters (no phi nodes)
149 │ ├── opt/ Optimization passes and pass manager
150 │ ├── codegen/ ARM64 instruction selection, linear scan register allocation
151 │ ├── driver/ CLI, compilation orchestration
152 │ └── runtime/ libarmfortas_rt — I/O, intrinsics, memory management
153 ├── bencch/ Compiler benchmark and test harness (git submodule)
154 ├── test_programs/ ~110 end-to-end test programs with CHECK annotations
155 └── runtime/ Runtime library source
156 ```
157
158 ### Key design decisions
159
160 **No LLVM.** gfortran's bugs are in GCC's backend. flang's bugs are in LLVM's frontend lowering. Using either as a backend would mean inheriting the bugs we're trying to escape. We own every pass.
161
162 **SSA IR with block parameters.** Instead of phi nodes, blocks carry typed parameters. Cleaner to construct, easier to verify, simpler to transform. The mem2reg pass promotes stack allocas to SSA values using iterated dominance frontiers (Cytron et al.).
163
164 **Apple AAPCS64 strictly.** 16-byte stack alignment always, x18 reserved, x29/x30 saved in prologue, frame pointer maintained. We've been bitten by every one of these constraints and handle them correctly.
165
166 **Array descriptors.** `{base_addr, elem_size, rank, flags, dims[15]}`. Our ABI — stable across releases.
167
168 **String descriptors.** `{data, len, capacity, flags}`. Deferred-length assignment always allocates new storage before freeing old. This prevents the use-after-free that causes gfortran's ARM64 allocatable string crashes.
169
170 **Large arrays on heap.** Stack threshold at 64KB. Prevents the stack corruption gfortran exhibits with arrays over ~600KB.
171
172 **afs-as** is the standalone ARM64 assembler. It knows nothing about Fortran — clean API boundary. It can be used independently to assemble ARM64 `.s` files.
173
174 ## Testing
175
176 ```bash
177 cargo test --workspace # all unit + integration tests
178 cargo test --test run_programs # end-to-end at -O0
179 cargo test --test run_programs -- --nocapture # verbose output
180 cargo run -p afs-tests -- run --suite runtime # bencch runtime suite
181 cargo run -p afs-tests -- run --suite consistency # reproducibility checks
182 ```
183
184
185 The root `armfortas` harness is the fast, armfortas-first runner. It compiles
186 each `.f90` file in `test_programs/`, runs the binary, and evaluates
187 source-embedded assertions such as:
188
189 - `! CHECK:` for stdout
190 - `! STDERR_CHECK:` for runtime stderr
191 - `! EXIT_CODE:` for exact runtime exit status
192 - `! XFAIL:` for known open bugs
193 - `! ERROR_EXPECTED:` for diagnostics that must be emitted
194 - `! ERROR_SPAN:` for exact diagnostic location
195 - `! ASM_CHECK:` / `! ASM_NOT:` for assembly shape
196 - `! FILE_CHECK:` / `! FILE_NOT:` for sandbox file side effects
197 - `! FILE_EXISTS:` / `! FILE_MISSING:` for explicit sandbox presence or absence
198 - `! FILE_LINE_COUNT:` for structural file-shape assertions
199 - `! FILE_RERUN_MODE:` for explicit overwrite vs append intent across reruns
200 - `! FILE_SET_EXACT:` for exact runtime side-effect file sets
201 - `! REPRO_CHECK:` for per-test asm/object/run reproducibility
202 - `! OPT_EQ:` for explicit cross-opt invariants
203 - `! PHASE_TRIANGULATE:` for same-opt IR/ASM/object availability, compile-cleanliness, and compile-only reproducibility oracles
204 - `! IR_CHECK:` / `! IR_NOT:` for IR shape
205
206 Those source comments are the canonical leaf-assertion language for the
207 project. The root harness is where new annotation ideas should land first.
208
209 `bencch` is the structured matrix/reporting/differential runner around that
210 same testing language. It is best for:
211
212 - opt matrices
213 - differential/reference runs
214 - module graphs
215 - capability-aware execution
216 - reports and bundles
217
218 The two surfaces are meant to converge on syntax and expectations, not drift
219 into separate testing dialects.
220
221 All root end-to-end tests run at every optimization level (`-O0` through
222 `-Ofast`). Programs with known bugs carry `! XFAIL:` annotations that reference
223 the audit finding — they count as passing until the bug is fixed, at which
224 point CI catches the unexpected success.
225
226 ## Target
227
228 - **Architecture**: ARM64 (AArch64), Apple Silicon (M1/M2/M3/M4)
229 - **OS**: macOS (Mach-O, Apple AAPCS64)
230 - **Standard**: F77 through F2018, building inward from F2018
231 - **Goal**: Compile fortsh — a 57,000-line Fortran 2018 shell — correctly on Apple Silicon
232
233 Compiling fortsh is a milestone, not the finish line. A complete compiler handles code fortsh never exercises.
234
235 ## Relationship to fortsh
236
237 This compiler exists because fortsh exists. Building a non-trivial Fortran program on ARM64 and discovering that neither available compiler handles it reliably was the motivation. The goal is a compiler we can fix when it breaks, running on the hardware we actually use.