Compiler driver and pipeline
armfortas
Source-backed guide to the armfortas driver, frontend, IR pipeline, current workflows, and every flag the CLI parser accepts today.
Open repositoryOverview
armfortas is the user-facing compiler binary and the workspace entry point for the ARM64 Fortran stack. The actual process control lives in src/lib.rs and src/driver/mod.rs: arguments are parsed, inputs are classified as sources or link artifacts, and then the driver dispatches into compile, multi-file compile, or link-only modes.
The most important source-level detail is that the current driver still shells out to the system assembler and system linker by default. AFS_AS_PATH and AFS_LD_PATH can override that path, but the default compile flow in the codebase is still .f90 -> assembly -> system as -> system ld. That makes src/driver/mod.rs more trustworthy than top-level narrative copy whenever the two disagree.
The repo layout lines up with compiler stages: src/preprocess, src/lexer, src/parser, src/sema, src/ir, src/opt, src/codegen, src/runtime, plus the separate runtime crate in runtime/and end-to-end fixtures in test_programs/.
Source of truth for current behavior: trust the driver, tests, and submodule source trees first; use the README as project context, not as the only specification.
Build and host setup
The repo is a Cargo workspace with git submodules. For a clean checkout, pull the submodules up front and build from the workspace root:
git clone --recurse-submodules git@github.com:FortranGoingOnForty/armfortas.git
cd armfortas
cargo build --workspace
cargo test --workspace
cargo clippy --workspace --exclude bencch-core --exclude afs-tests -- -D warnings -A clippy::too_many_argumentsIf you already cloned without recursion, run git submodule update --init --recursive. That lands afs-as/, afs-ld/, and bencch/, all of which the workspace and docs assume exist.
The codebase is explicitly Apple Silicon oriented. The driver asks xcrunfor the SDK path and invokes as and ld directly, so a working Xcode command-line toolchain is part of the practical host setup even before you opt into afs-as or afs-ld.
The runtime archive libarmfortas_rt.a is discovered from target/debug, target/release, sibling install locations, or the AFS_RUNTIME_PATH override. You do not normally need to copy it by hand.
Common workflows
Compile and link one program
target/debug/armfortas hello.f90 -o hello
./helloStop at an intermediate artifact
target/debug/armfortas -E hello.f90
target/debug/armfortas --emit-tokens hello.f90
target/debug/armfortas --emit-ast hello.f90
target/debug/armfortas --emit-ir hello.f90
target/debug/armfortas -S hello.f90
target/debug/armfortas -c hello.f90Compile multiple module-aware sources
target/debug/armfortas main.f90 math_mod.f90 io_mod.f90 -J build/amod -o appThe multi-file path scans module dependencies, topologically sorts the compilation order, emits per-file objects, and then links the resulting object list. If you pass -c to that mode, the driver stops after writing each object file.
Link prebuilt objects and archives
target/debug/armfortas hello.o support.o libextra.a -o helloPure link-artifact inputs are allowed. What is not allowed yet is a mixed invocation like armfortas hello.f90 support.o; the driver explicitly errors and asks you to compile the source first, then link the objects in a second step.
Override the assembler or linker
AFS_AS_PATH=$PWD/target/debug/afs-as target/debug/armfortas hello.f90 -c
AFS_LD_PATH=$PWD/target/debug/afs-ld target/debug/armfortas hello.o -o helloThe linker override is intentionally narrower than the default system-ldpath right now. The driver rejects shared-library links, -L, -l, -rpath, and -static when AFS_LD_PATH is set.
Pipeline and artifacts
The compile path in src/driver/mod.rs is explicit and linear: read source, detect or override source form, preprocess, lex, parse, resolve and validate semantically, lower to SSA IR, run the optimization pipeline, select ARM64 machine instructions, allocate registers, emit assembly text, assemble to Mach-O, and finally link.
Output naming is deterministic. Without -o, the driver derives .s, .o, .ir, .ast, or .tokens from the input stem depending on the stop point. Linked outputs default to the bare input stem, while pure link-only mode defaults to a.out.
Module units trigger .amod emission after object generation. If you pass -J dir, generated module interfaces are written there; otherwise they follow the primary output directory. The same module output directory is also added to the module search path so later files can consume newly-written interfaces.
Process exit codes are defined in src/lib.rs: 0 success, 1 compile error, 2 link error, 3 I/O error, and 4 internal compiler error. For an ICE, rerun with RUST_BACKTRACE=1.
CLI and environment reference
The parser is hand-rolled in src/driver/mod.rs. It accepts joined short forms for options like -I, -J, -L, -l, -D, -o, and optimization flags like -O2.
Information and response-file flags
| Flag | Behavior | Notes |
|---|---|---|
--help, -h | Print help text and exit. | If no args are passed, the driver prints the same help text and exits 0. |
--version, -V | Print program name plus version. | The string is emitted as `<program> <version> (aarch64-apple-darwin)`. |
-dumpversion | Print only the version number. | If multiple info flags are supplied, the last one wins. |
@file | Expand a response file into additional CLI tokens. | Nested response files are supported up to depth 8; `@@arg` escapes a literal leading `@`. |
Compilation and output flags
| Flag | Behavior | Notes |
|---|---|---|
-c | Compile to an object file and stop before linking. | Default output is `<stem>.o` unless `-o` is supplied. |
-S | Emit assembly text and stop before object generation. | Writes `<stem>.s` by default. |
-E | Run the preprocessor only. | Writes to stdout if `-o` is omitted or set to `-`. |
--emit-ir | Dump the SSA IR printer output. | Writes `<stem>.ir` by default and returns before codegen. |
--emit-ast | Dump the parsed AST. | Writes `<stem>.ast` and returns after parsing. |
--emit-tokens | Dump lexer tokens. | Writes `<stem>.tokens` and returns after lexing. |
-o <path> | Override the output path. | Short joined forms like `-oout` and `-o=out` are accepted. |
-cpp | Accept the GNU preprocessing flag. | Recognized for compatibility; the compiler already preprocesses Fortran inputs. |
-D<name>[=<value>] | Define a preprocessor macro. | If no value is supplied, the macro defaults to `1`. |
Language and semantics flags
| Flag | Behavior | Notes |
|---|---|---|
-std / --std | Select a Fortran standard. | Accepted values: `f77`, `f90`, `f95`, `f2003`, `f2008`, `f2018`, `f2023`. |
-ffree-form | Force free-form source parsing. | Overrides file-extension-based detection. |
-ffixed-form | Force fixed-form source parsing. | Required if you want `--std=f77` on a non-`.f` path. |
-fdefault-integer-8 | Set unsuffixed `integer` declarations to kind 8. | Implemented through thread-local defaults in `src/driver/defaults.rs`. |
-fdefault-real-8 | Set unsuffixed `real` declarations to kind 8. | Also wired through the driver defaults layer. |
-fimplicit-none | Force implicit none across all scopes. | Applied after resolution via the symbol-table state. |
-frecursive | Accept a GNU-style recursive-default switch. | Recognized today, but the driver emits a CLI warning that it is not implemented yet. |
-fbackslash / -fno-backslash | Toggle backslash escape handling in string literals. | `-fbackslash` is recognized, but escape processing is not implemented yet. |
-fmax-stack-var-size=<n> | Accept a stack-threshold tuning knob. | Recognized, but the driver warns that it is not implemented yet. |
Optimization, warnings, and debugging flags
| Flag | Behavior | Notes |
|---|---|---|
-O0, -O1, -O2, -O3, -Os, -Ofast | Choose the IR optimization pipeline. | `-O0` preserves the raw IR shape; higher levels run the pass manager and backend peepholes. |
-Wall, -Wextra | Accept common warning-group toggles. | Recognized, but the driver currently warns that warning-group emission is still being implemented. |
-Wpedantic, -pedantic | Enable pedantic standard-conformance diagnostics. | Feeds into semantic validation. |
-Wdeprecated | Enable deprecated-feature diagnostics. | Also feeds into semantic validation. |
-Werror | Promote CLI warnings to hard failures. | This matters today mainly for recognized-but-unimplemented compatibility flags. |
-Wno-<name> | Disable a specific warning bucket. | Includes `unknown-warning-option`, which suppresses complaints about unknown `-W...` flags. |
-g, -g0..-g3 | Accept debug-info switches. | Recognized, but DWARF emission is still marked TODO and the driver warns accordingly. |
-fbacktrace | Accept runtime-backtrace control. | Recognized today, but runtime backtrace wiring is not implemented yet. |
-fcheck=bounds, -fcheck=all | Accept runtime-check flags. | The driver warns that array bounds checks are already always enabled, so these are compatibility switches right now. |
-v, --verbose | Print phase-level progress to stderr. | Useful for source-form detection, emitted artifacts, and linked outputs. |
--time-report | Print wall-clock timing per compiler phase. | Timings are tracked by the `PhaseTimer` in `src/driver/mod.rs`. |
--diagnostics-format=text|json | Select diagnostic serialization format. | `text` is the only implemented mode today; `json` currently errors during CLI parsing. |
Search-path and linker flags
| Flag | Behavior | Notes |
|---|---|---|
-I <dir> | Add a module/include search path. | Used when resolving `.amod` module interface files. |
-J <dir> | Choose the output directory for generated `.amod` files. | The same directory is also added to the module search path if needed. |
-L <dir> | Forward a library search path to the linker. | Joined forms like `-L/path` are accepted. |
-l <lib> | Forward a library input to the linker. | Joined forms like `-lSystem` are accepted. |
-rpath, --rpath <path> | Pass an LC_RPATH entry through to the linker. | Supported on the system-`ld` path; currently rejected if `AFS_LD_PATH` is used. |
-shared | Request a shared-library link. | Mapped to `-dylib` on Apple `ld`. |
-static | Bias the link toward static archives. | Apple `ld` has no true `-static`; the driver forwards `-search_paths_first` instead. |
<files...> | Provide one or more source files or prebuilt link artifacts. | Multiple sources trigger the module-aware multi-file driver; pure object/archive inputs perform a link-only path. Mixing the two is still rejected. |
Environment variables
AFS_AS_PATH: use a custom assembler binary instead of the systemas.AFS_LD_PATH: use a custom linker binary instead of the systemld; currently limited to simple executable links.AFS_RUNTIME_PATH: point directly atlibarmfortas_rt.aor a directory containing it.ARMFORTAS_USE_NAIVE_REGALLOC: force the naive register allocator instead of linear scan.RUST_BACKTRACE=1: include a stack trace when the driver reports an internal compiler error.
Testing and regression model
The repo uses layered verification rather than one golden-path smoke test. CI builds the workspace, runs clippy, executes the library unit tests, runs the end-to-end harness, and then runs the broader integration matrix under tests/.
cargo test --workspace
cargo test --test run_programs -- --nocapture
cargo test -p armfortas --tests --release -- --nocaptureThe end-to-end harness in tests/run_programs.rs is worth reading directly. It recognizes source annotations such as ! CHECK:, ! ERROR_EXPECTED:, ! XFAIL:, ! STDERR_CHECK:, ! ASM_CHECK:, ! IR_CHECK:, ! OPT_EQ:, and the broader ! PHASE_TRIANGULATE: family.
The core invariant is cross-optimization correctness: a program that is correct at -O0 should remain correct at every higher optimization level the driver accepts. The test harness treats unexpected XFAIL success as a failure so fixed bugs get cleaned up.
Current edges
The source tree is further along than a toy compiler, but a few gaps are explicit in the code and README. Multi-file module support exists in the driver and test suite, yet the project README still describes `.amod` work as in progress. The safe reading is that the feature exists but is still evolving.
Other intentionally partial surfaces are also visible in the parser: JSON diagnostics are recognized but not implemented; -g, -fbacktrace, -frecursive, -fbackslash, and -fmax-stack-var-size are accepted, but each currently emits a warning or compatibility note rather than unlocking a fully finished behavior.
If you are debugging real output, use the driver source, generated artifacts, and the harness annotations as the operational specification. They are the freshest view of what the compiler actually does today.