We’re thrilled to share that LFortran can now successfully compile and execute libprima/PRIMA. PRIMA marks the eighth production-grade, third-party code that LFortran has compiled with bit-for-bit alignment to GFortran’s output. This milestone brings us closer to our goal of compiling 10 such codes—an essential step toward achieving a beta-quality compiler.


About PRIMA

PRIMA is a package developed by Zaikun Zhang for solving general nonlinear optimization problems without using derivatives. It provides the reference implementation for Powell’s derivative-free optimization methods, i.e., COBYLA, UOBYQA, NEWUOA, BOBYQA, and LINCOA. PRIMA means Reference Implementation for Powell’s methods with Modernization and Amelioration, P for Powell. The package is written in Fortran and is widely used in the scientific community for solving optimization problems.

It utilizes a range of Modern Fortran features, including extensive use of optional variables, function pointer passing, and a randomized test driver, among others. Successfully compiling PRIMA requires a compiler with a robust and mature backend, as well as well-developed intermediate passes and a capable parser.

How to Compile PRIMA with LFortran

Follow the steps shown below to build, compile and run PRIMA examples and tests with LFortran.

Set up the Environment

conda create -n lfortran lfortran=0.46.0 make cmake
conda activate lfortran
git clone https://github.com/Pranavchiku/prima.git
cd prima
git checkout -t origin/lf-prima-12

For macos

export LFORTRAN_RUNNER_OS='macos';

For linux

export LFORTRAN_RUNNER_OS='linux';
git clean -dfx
FC="lfortran --cpp" cmake -S . -B build -DCMAKE_INSTALL_PREFIX=$(pwd)/install  && cmake --build build --target install

To execute examples

./build/fortran/example_bobyqa_fortran_1_exe
./build/fortran/example_bobyqa_fortran_2_exe
./build/fortran/example_cobyla_fortran_1_exe
./build/fortran/example_cobyla_fortran_2_exe
./build/fortran/example_lincoa_fortran_1_exe
./build/fortran/example_lincoa_fortran_2_exe
./build/fortran/example_newuoa_fortran_1_exe
./build/fortran/example_newuoa_fortran_2_exe
./build/fortran/example_uobyqa_fortran_1_exe
./build/fortran/example_uobyqa_fortran_2_exe

To execute tests

cd fortran/
test_name=test_xx.f90 FC=lfortran ./script.sh
cd ../

Build with optimisations

git clean -dfx
FC="lfortran --cpp --fast" cmake -S . -B build -DCMAKE_INSTALL_PREFIX=$(pwd)/install  && cmake --build build --target install

Compilation Benchmarks

To ensure no performance loss, we conducted several benchmarks comparing LFortran against GFortran, both without any optimisations when compiling PRIMA. For these tests, we used lfortran=0.46.0 on a MacBook Air M2 with 8GB of RAM.

For compiling single binary ( for example: cobyla ), one has to apply the following diff and then compile the code.

diff --git a/fortran/CMakeLists.txt b/fortran/CMakeLists.txt
index cf16524a..b1673397 100644
--- a/fortran/CMakeLists.txt
+++ b/fortran/CMakeLists.txt
@@ -147,13 +147,13 @@ macro (prima_add_f_test name number)
   add_dependencies(examples example_${name}_fortran_${number}_exe)
 endmacro ()
 
-prima_add_f_test (cobyla 1)
+# prima_add_f_test (cobyla 1)
 prima_add_f_test (cobyla 2)
-prima_add_f_test (bobyqa 1)
-prima_add_f_test (bobyqa 2)
-prima_add_f_test (newuoa 1)
-prima_add_f_test (newuoa 2)
-prima_add_f_test (uobyqa 1)
-prima_add_f_test (uobyqa 2)
-prima_add_f_test (lincoa 1)
-prima_add_f_test (lincoa 2)
+# prima_add_f_test (bobyqa 1)
+# prima_add_f_test (bobyqa 2)
+# prima_add_f_test (newuoa 1)
+# prima_add_f_test (newuoa 2)
+# prima_add_f_test (uobyqa 1)
+# prima_add_f_test (uobyqa 2)
+# prima_add_f_test (lincoa 1)
+# prima_add_f_test (lincoa 2)
Test Scenario Compiler Command Time
Compiling cobyla LFortran 0.46.0 time FC="lfortran --cpp" cmake -S . -B build -DCMAKE_INSTALL_PREFIX=$(pwd)/install && time cmake --build build --target install 5.284
GFortran 14.2.0 FC=gfortran cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install -DCMAKE_Fortran_COMPILER=gfortran && time cmake --build build --target install 6.881

Example Benchmarks

Example Compiler Command Time
bobyqa_example_1 LFortran 0.46.0 time ./build/fortran/example_bobyqa_fortran_1_exe 0.029s
GFortran 14.2.0 time ./build/fortran/example_bobyqa_fortran_1_exe 0.015s
bobyqa_example_2 LFortran 0.46.0 time ./build/fortran/example_bobyqa_fortran_2_exe 0.36s
GFortran 14.2.0 time ./build/fortran/example_bobyqa_fortran_2_exe 0.166s
cobyla_example_1 LFortran 0.46.0 time ./build/fortran/example_cobyla_fortran_1_exe 0.03s
GFortran 14.2.0 time ./build/fortran/example_cobyla_fortran_1_exe 0.015s
cobyla_example_2 LFortran 0.46.0 time ./build/fortran/example_cobyla_fortran_2_exe 0.19s
GFortran 14.2.0 time ./build/fortran/example_cobyla_fortran_2_exe 0.087s
lincoa_example_1 LFortran 0.46.0 time ./build/fortran/example_lincoa_fortran_1_exe 0.033s
GFortran 14.2.0 time ./build/fortran/example_lincoa_fortran_1_exe 0.016s
lincoa_example_2 LFortran 0.46.0 time ./build/fortran/example_lincoa_fortran_2_exe 3.43s
GFortran 14.2.0 time ./build/fortran/example_lincoa_fortran_2_exe 0.099s
newuoa_example_1 LFortran 0.46.0 time ./build/fortran/example_newuoa_fortran_1_exe 0.026s
GFortran 14.2.0 time ./build/fortran/example_newuoa_fortran_1_exe 0.011s
newuoa_example_2 LFortran 0.46.0 time ./build/fortran/example_newuoa_fortran_2_exe 0.05s
GFortran 14.2.0 time ./build/fortran/example_newuoa_fortran_2_exe 0.037s
uobyqa_example_1 LFortran 0.46.0 time ./build/fortran/example_uobyqa_fortran_1_exe 0.02s
GFortran 14.2.0 time ./build/fortran/example_uobyqa_fortran_1_exe 0.01s
uobyqa_example_2 LFortran 0.46.0 time ./build/fortran/example_uobyqa_fortran_2_exe 0.05s
GFortran 14.2.0 time ./build/fortran/example_uobyqa_fortran_2_exe 0.02s

We’re pleased to report that LFortran compiles the PRIMA code as is—without any workarounds or modifications—except for the necessary adjustments to integrate LFortran into the existing PRIMA build system.

Development Overview

PRIMA has been on our list since we set out to achieve our goal of compiling 10 production-level codes. However, compiling it was no cakewalk—it came with its own set of challenges. Despite this, our team’s collaborative effort and determination allowed us to tackle them effectively. Here are some key areas where we focused our development efforts:

Strengthening support for procedure variables

PRIMA extensively utilizes function pointers as arguments, relying on procedure variables for linking. This posed a challenge due to LFortran’s initial improper handling of procedure variables in its intermediate passes. Addressing this issue required multiple pull requests, which can be found under label-prima.

Passing arrays with unequal strides not equal to one to functions

PRIMA presented cases where array sections with non-unit strides were passed to functions, requiring us to generate temporary arrays at runtime. To handle this efficiently, we developed a dedicated pass to detect such cases and create the necessary temporaries. The implementation details can be found at PR#6363

Precision loss while using power ** operator

We encountered precision loss issues in PRIMA when using the power operator **, particularly with double-precision variables with large exponents. This led to incorrect results for expressions like v**3, as LFortran internally cast the exponent to real(3, kind=8). This issue was resolved by using llvm.powi through a dedicated PR#.

Along with these there were several other issues that were resolved to successfully compile PRIMA with LFortran. The complete list of issues and PRs can be found here.

What’s Next?

As of this writing, LFortran compiles eight third-party codes:

Here is our issue to track priorities to reach beta quality.

Our primary objective is to transition LFortran from alpha to beta, and we believe that successfully compiling 10 third-party codes is a key milestone in this journey. Our strategy prioritizes features based on their significance, ensuring that LFortran fully supports all language constructs used in these selected codes.

Progress toward beta will be measured by our ability to compile and execute these codes without modifications. Currently, another team is working on POT3D, which is nearly compiling but requires further refinement. We will continue announcing each successful compilation as LFortran achieves full compatibility.

Next, we plan to target Fortran Package Manager (fpm), LAPACK, other codes listed under label:code-to-be-compiled, and the remaining parts of Fortran stdlib and SciPy.

Once we reach our goal of compiling 10 third-party codes, we will collaborate with the community to determine additional steps needed for beta. In our definition, a beta-quality compiler is one that successfully executes user code without errors, though it may still contain some bugs.

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 ample opportunities for learning and growth.

Acknowledgements

We want to thank:

Discussions