LFortran can now compile fpm, the Fortran Package Manager. We opened up an
issue for it in April
2025, when we started focusing on it as a priority. We closed it on
February 7, 2026. fpm is the most complex project that we now successfully
built and run. It is an interesting project: it is not computational, but
rather a system project which exercises running other programs, reading
environment variables, command-line argument parsing, reading and writing
files, directories, parsing enough Fortran to understand dependencies, etc. It
uses almost all modern Fortran features: classes, inheritance, allocatable
components, constructors, arrays of classes, select type, associate, automatic
LHS (left-hand side) reallocation, strings, arrays of strings and it exposed
dozens and dozens of bugs and missing features in LFortran. And we have now
implemented them all. As a result, LFortran is really close to beta, and we are
advancing our progress bar to 9/10.
When we started our progress bar towards beta, we thought that compiling 10
codes with increasing complexity and ending with fpm would be enough for beta.
But when we got close to compiling fpm, we realized we are still not quite
there. Beta quality to us means that when you use LFortran on your code of N
lines (say N=10,000), it will compile and run it correctly in about 90% of
the cases. We are estimating that we are there with N=500 or maybe N=1,000,
but not for larger codes yet. However the bugs are very small, and so we are
sprinting by compiling more and more codes and fixing all bugs, which are now
simple to fix.
So we decided to cheat a little bit and did not progress the bar on compiling LAPACK, which allows us to progress the bar now and still have one step left and we will progress it to beta once we are confident that we can compile medium-sized codes with high probability.
One of the big subprojects was to completely refactor how we handle classes, virtual functions and inheritance. We studied how Clang handles classes in C++ and implemented a similar approach with some Fortran-specific additions.
As with any big projects that we compile, we got it working a few weeks ago,
we set up testing that compiles and runs all fpm tests at our CI (Continuous
Integration) for every commit and then we keep fixing bugs from other projects
and watch the CI if it ever fails in fpm with flaky bugs. If it does not
after lots of other PRs (Pull Requests) are merged, we know that it is working
and that it does not have simple flaky bugs. It might still have some bugs that
do not show up easily, but all the things that we can see are rock solid, so we
are comfortable announcing it.
We also added support for fpm’s five key dependencies:
M_CLI2 command-line argument parsing),
toml-f (reads TOML configuration files),
fortran-regex (handles pattern matching),
fortran-shlex (processes shell-like syntax),
and Jonquil (manages JSON data).
Build
To build, do:
git clone https://github.com/fortran-lang/fpm
git checkout d0f89957541bdcc354da8e11422f5efcf9fedd0e # latest main
conda create -n fpm lfortran=0.60.0 fpm gfortran
conda activate fpm
fpm --compiler=lfortran test --flag --cpp --flag --realloc-lhs-arrays --flag --use-loop-variable-after-loop
You can also append --fast or --separate-compilation options, both work as
well. The gfortran compiler is necessary to pass tests, it is not
being used to build any source code. Upstream would need to be improved to not
have gfortran hard-coded in testing.
LFortran by default has bounds checking on, and it will give runtime errors when an incorrect shape or an unallocated array is used. This found a genuine upstream bug that we fixed by submitting a PR upstream. Over time we will harden our compile-time and runtime checks so that your code either works or gives an error at compile-time or runtime, but never segfaults or has other undefined behavior.
Speed of Compilation
To get some idea about the speed of compilation, here are the times on Apple M4 MacBook Pro:
$ time fpm build --compiler=gfortran
[...]
libfpm.a done.
main.f90 done.
fpm done.
[100%] Project compiled successfully.
real 42.342s
user 39.902s
sys 2.050s
cpu 99.1%
$ time fpm --compiler=lfortran build --flag --cpp --flag --realloc-lhs-arrays --flag --use-loop-variable-after-loop
[...]
real 16.641s
user 15.533s
sys 0.968s
cpu 99.2%
The exact compiler versions are:
$ gfortran --version
GNU Fortran (conda-forge gcc 15.2.0-18) 15.2.0
$ lfortran --version
LFortran version: 0.60.0
Platform: macOS ARM
LLVM: 19.1.1
Default target: arm64-apple-darwin24.6.0
LSP Version: 3.17.0
JSON_RPC Version: 2.0
Both are single-core compilation of the fpm binary. Overall, not bad for a
start.
To get a better idea where LFortran is spending compilation time, you can
rerun fpm with --verbose and then manually run longest compilation command,
which happens to be:
lfortran -c app/main.f90 --cpp --cpp --realloc-lhs-arrays --use-loop-variable-after-loop -DFPM_RELEASE_VERSION=0.12.0 -Ibuild/dependencies/fortran-regex/src -J build/lfortran_29F2E0FA2D75FE0A -Ibuild/lfortran_29F2E0FA2D75FE0A -Ibuild/lfortran_5D5DD1C987059777 -o build/lfortran_29F2E0FA2D75FE0A/fpm/app_main.f90.o --time-report
Notice the --time-report option that we added at the end. This prints:
Allocator usage of last chunk (MB) 0.284
Allocator chunks 7.000
------------------------------------------------------------
Component name Time (ms)
------------------------------------------------------------
File reading 0.450
Src -> ASR 93.267
ASR passes (total) 178.630
global_stmts 0.000
init_expr 1.885
function_call_in_declaration 2.686
openmp 0.000
implied_do_loops 4.687
array_struct_temporary 13.439
transform_optional_argument_functions 11.940
nested_vars 8.111
forall 1.281
class_constructor 4.553
pass_list_expr 4.867
where 1.652
array_op 4.886
symbolic 0.708
intrinsic_function 29.437
intrinsic_subroutine 5.336
subroutine_from_function 7.500
array_op 5.352
pass_array_by_data 16.393
array_passed_in_function_call 6.463
print_list_tuple 1.797
array_dim_intrinsics_update 6.731
do_loops 6.631
while_else 3.165
select_case 3.459
unused_functions 21.647
unique_symbols 0.000
insert_deallocate 3.432
other processing time 0.026
ASR -> mod 0.410
LLVM IR creation 329.712
LLVM opt 0.000
LLVM -> BIN 12539.240
------------------------------------------------------------
Total time 13204.171
------------------------------------------------------------
Here, ASR means Abstract Semantic Representation, the main internal representation (IR) that LFortran uses. We then run a similar line to figure out how long it takes to link.
So here is the breakdown:
| Task | Time [s] | Percentage |
|---|---|---|
| Compiling all files to one ASR | 3.5s | 21.1% |
| ASR->ASR passes | 0.2s | 1.2% |
| ASR->LLVM IR | 0.3s | 1.8% |
| LLVM IR -> object file | 12.5s | 75.3 % |
| Linking | 0.1s | 0.6% |
| Total | 16.6 | 100% |
The main bottleneck is LLVM IR compilation to binary object code (over 3/4 of all the time). This excludes creating the LLVM IR from ASR as well as linking (both counted on separate lines above). To speed this up, we plan to create our own backend later, we think it might be possible to make it compile about 10x faster, which would bring the total time down to about 5s.
After that, we can start optimizing the other parts.
The first line, compilation of all files to one ASR, includes parsing all
files, doing semantic analysis, loading all dependent Fortran modules’ .mod
files, and saving a .mod file for a given module.
That is not the most efficient way to compile it: a better way would be for the
build system (fpm in this case) to give LFortran the dependency graph and
files to compile and compiler options for each file and LFortran would compile
all the files at once, never saving or loading .mod files from the disk,
assembling the final ASR step by step.
It is a very interesting computational problem of taking source code and emitting a binary as fast as we can, and we are excited to start working on that after we reach beta.
Going Forward
Now that we reached this big milestone, we are working on other third-party codes and fixing all bugs that we encounter. We are still in alpha stage. When we feel comfortable expecting LFortran to just work on medium-sized codes, we will announce beta quality. It is becoming easier and easier to compile a new third-party code, the bugs are getting smaller and less frequent. As far as we know, all Fortran features are implemented, except coarrays and parametrized derived types (PDT), which we will tackle after beta. It is hard to predict a timeline, but our best guess is a timeframe of several months (so not weeks, but also not years). We will see if we were right.
We will keep announcing every new significant third-party code that we can compile.
Join Us
We welcome new contributors to join our journey. If you’re interested, please reach out on Zulip. Working on LFortran is both challenging and rewarding, offering great opportunities for learning and growth.
Acknowledgements
We want to thank:
- Sovereign Tech Fund (STF)
- NumFOCUS
- QuantStack
- Google Summer of Code
- FLOSS/fund
- John D. Cook
- LANL
- GSI Technology
- Our GitHub, OpenCollective and NumFOCUS sponsors
- All our contributors (131 so far!)