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 repository

Overview

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_arguments

If 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
./hello

Stop 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.f90

Compile multiple module-aware sources

target/debug/armfortas main.f90 math_mod.f90 io_mod.f90 -J build/amod -o app

The 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 hello

Pure 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 hello

The 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

FlagBehaviorNotes
--help, -hPrint help text and exit.If no args are passed, the driver prints the same help text and exits 0.
--version, -VPrint program name plus version.The string is emitted as `<program> <version> (aarch64-apple-darwin)`.
-dumpversionPrint only the version number.If multiple info flags are supplied, the last one wins.
@fileExpand 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

FlagBehaviorNotes
-cCompile to an object file and stop before linking.Default output is `<stem>.o` unless `-o` is supplied.
-SEmit assembly text and stop before object generation.Writes `<stem>.s` by default.
-ERun the preprocessor only.Writes to stdout if `-o` is omitted or set to `-`.
--emit-irDump the SSA IR printer output.Writes `<stem>.ir` by default and returns before codegen.
--emit-astDump the parsed AST.Writes `<stem>.ast` and returns after parsing.
--emit-tokensDump 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.
-cppAccept 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

FlagBehaviorNotes
-std / --stdSelect a Fortran standard.Accepted values: `f77`, `f90`, `f95`, `f2003`, `f2008`, `f2018`, `f2023`.
-ffree-formForce free-form source parsing.Overrides file-extension-based detection.
-ffixed-formForce fixed-form source parsing.Required if you want `--std=f77` on a non-`.f` path.
-fdefault-integer-8Set unsuffixed `integer` declarations to kind 8.Implemented through thread-local defaults in `src/driver/defaults.rs`.
-fdefault-real-8Set unsuffixed `real` declarations to kind 8.Also wired through the driver defaults layer.
-fimplicit-noneForce implicit none across all scopes.Applied after resolution via the symbol-table state.
-frecursiveAccept a GNU-style recursive-default switch.Recognized today, but the driver emits a CLI warning that it is not implemented yet.
-fbackslash / -fno-backslashToggle 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

FlagBehaviorNotes
-O0, -O1, -O2, -O3, -Os, -OfastChoose the IR optimization pipeline.`-O0` preserves the raw IR shape; higher levels run the pass manager and backend peepholes.
-Wall, -WextraAccept common warning-group toggles.Recognized, but the driver currently warns that warning-group emission is still being implemented.
-Wpedantic, -pedanticEnable pedantic standard-conformance diagnostics.Feeds into semantic validation.
-WdeprecatedEnable deprecated-feature diagnostics.Also feeds into semantic validation.
-WerrorPromote 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..-g3Accept debug-info switches.Recognized, but DWARF emission is still marked TODO and the driver warns accordingly.
-fbacktraceAccept runtime-backtrace control.Recognized today, but runtime backtrace wiring is not implemented yet.
-fcheck=bounds, -fcheck=allAccept runtime-check flags.The driver warns that array bounds checks are already always enabled, so these are compatibility switches right now.
-v, --verbosePrint phase-level progress to stderr.Useful for source-form detection, emitted artifacts, and linked outputs.
--time-reportPrint wall-clock timing per compiler phase.Timings are tracked by the `PhaseTimer` in `src/driver/mod.rs`.
--diagnostics-format=text|jsonSelect diagnostic serialization format.`text` is the only implemented mode today; `json` currently errors during CLI parsing.

Search-path and linker flags

FlagBehaviorNotes
-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.
-sharedRequest a shared-library link.Mapped to `-dylib` on Apple `ld`.
-staticBias 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 system as.
  • AFS_LD_PATH: use a custom linker binary instead of the system ld; currently limited to simple executable links.
  • AFS_RUNTIME_PATH: point directly at libarmfortas_rt.a or 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 -- --nocapture

The 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.