User defined functions in constant expressions

Currently, Fortran standard only allows intrinsic functions in a constant expression. In past, have used unwieldy, may be creative, combinations of intrinsics and constructors to initialize parameters. Even then, the scope remains limited, and the code quickly becomes unintelligible. Some languages, like C++ and nim, allow functions with suitable restrictions to be used in constant expressions. Is this my fetish with compile time evaluations that I desire such feature in Fortran, or others too see a requirement. As far as resulting savings at runtime are concerned: (a) usually they are not going to be huge and (b) perhaps compilers are smart enough to see through them without requiring explicit declaration.

Looking for feedback before adding feature request.

PS- If you are desirous of seeing the kind of gymanstics I do with constant expressions using instrinsics see (especially the evaluation of derivatives of Lagrange polynomials):

7 Likes

@mfurquan ,

I strongly encourage you to develop your ideas, collaborate with the Fortran Community as you deem appropriate, research options/solutions, and develop proposal(s) toward this. You may know of GitHub Fortran proposals site, you may want to consider at site for this:

“Constexpr as much as possible” or in other words, utilize as much compile-time computing as viable is a mantra that’s increasingly becoming important in many modern codes based on C++17 (and later revisions) toward scientific and technical computing in industry, especially in process simulations and modeling.

Improved constexpr in C++17 is particularly valuable when one needs to construct large named constants arrays for use in simulations based on other constants (e.g., coefficients in engineering correlations, results from prior simulation runs, results from first-principles models to be applied in reduced-order models, etc.). These are all use cases that are rather similar to the one you show well with the derivatives of Lagrange polynomials.

Sometime ago a team I was working with had to reuse certain case-based logic to construct some data in engineering calculations and they wanted to set it all up just as the libraries were loaded (c.f. Windows DLLs). It was a real pain in Fortran but a breeze with C++17 (supported well by Microsoft C++ processor). I even put together a MWE, ostensibly silly, for what they were trying to do having removed all their business logic of course:

// Example #  : MWE-9.F202Y.003
// Author     : FortranFan
// Reference  : CONSTEXPR precedures
//
// Description:
// An example in C++17 of constexpr functions that help with
// case-based logic to generate constant arrays at compile-time

#include <array>
#include <iostream>
using namespace std;

// Some base data
constexpr int N = 3;
constexpr std::array<int, N> vals = { 1, 2, 3 };
constexpr std::array<double, N> base_data = { 1.0, -2.0, 3.0 };

// Calculation from base data
constexpr int M = 5;
constexpr std::array<int, M> idx = { vals[2], vals[0], vals[1], vals[0], vals[1] };
// Some user function 1 
constexpr double user_func1(int n) {
   switch ( idx[n-1] )
   {
       case vals[0] : return base_data[0] + 2.0;   break;
       case vals[1] : return base_data[1] - base_data[2]; break;
       case vals[2] : return 0.0;  break;
       default      : return -1.0; break;
   }
}
// Load data at compile-time using user function 2
constexpr std::array<double, M> user_func2{ user_func1(1), user_func1(2), user_func1(3),
   user_func1(4), user_func1(5) };

int main() {
   for (int i = 0; i < M; i++) {
      std::cout << "user_func2[" << i << "] = " << user_func2[i] << "\n";
   }
   return 0;
}

C:\temp>cl /EHsc ce.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.27.29112 for x64
Copyright (C) Microsoft Corporation. All rights reserved.

ce.cpp
Microsoft (R) Incremental Linker Version 14.27.29112.0
Copyright (C) Microsoft Corporation. All rights reserved.

/out:ce.exe
ce.obj

C:\temp>ce.exe
user_func2[0] = 0
user_func2[1] = 3
user_func2[2] = -5
user_func2[3] = 3
user_func2[4] = -5

C:\temp>

The ability to have nonintrinsic i.e., user-defined functions that are of, say, CONSTEXPR prefix-spec in Fortran will be entirely consistent, in my opinion, with the evolution path of Fortran with PURE/ELEMENTAL procedures in Fortran 95 to SIMPLE procedures in Fortran 202X. Most of the semantics are already there in Fortran, I personally think it merely needs some adjustments to the language standard (tightening of requirements in some places, some loosening elsewhere).

So my suggestion to the Fortran committee and the Community will be to consider a new procedure prefix-spec CONSTEXPR that not only requires the procedure to be SIMPLE (the above-mentioned new Fortran 202X feature) but that each instruction in such a procedure either be constant expression or involve a CONSTEXPR procedure. Of course, the details are yet to be fully fleshed out.

The introduction of such a CONSTEXPR procedure in a future Fortran standard, say 202Y, will greatly enhance compile-time computing facilities in Fortran and I have no doubt that will be of great benefit in scientific and technical computing.

Kudos to you for bringing this up here.

Fyi, I’ve added an issue at the GitHub site, any comments and feedback by readers will be much appreciated:

I’ll admit I haven’t studied the words in the draft 202X standard closely for this, but it was my understanding that simple procedures were intended to be allowed in declarations in the way that only “constant expressions” were previously allowed, and that the constraints on simple procedures were sufficient to make them equivalent to constexpr. The standard may not require that a compiler take advantage of the ability to compute such functions at compile time in all cases, but that wouldn’t mean you couldn’t force it’s hand by declaring parameters to store the results.

Does anybody know of some reason that simple isn’t sufficient for use as an equivalent to constexpr? If so I’d be in favor of helping to correct any such deficiency before 202X is finished.

I’ve looked closely at the design of SIMPLE procedures (see link above), the working document toward the draft standard, particularly sections 10.1.2 on constant expression and also sections 15.8 on SIMPLE procedures, and also inquired with the relevant subgroup. My reading is nonintrinsic procedures, even if SIMPLE, are disallowed in constant expressions. See below.

Making a long story short, I’ll simply state a SIMPLE procedure is permitted to have executable code that is not a constant expression.

Well, I expect it will be a monumental challenge to convince the committee this is a “deficiency” in the worklist toward SIMPLE in 202X! At best, this can get marked as a new feature proposal. But my impression is trying to then get that into Fortran 202X will be deemed as “one-plussing” the next revision which will be a no-no per WG5. But I am nearly always wrong about such matters, or things tend to go perpendicular to what I expect or suggest. So please feel free to try to address this in 202X if you think it is worthwhile.

I thought about what it would take to allow this in LFortran.

Currently, all compile time evaluation is done in AST->ASR in LFortran. To evaluate things like sin, it is simply implemented in the compiler itself, independent of any runtime implementation. If we allow to execute any pure or simple function at compile time, then one would call the pure function via LLVM at compile time.

There is a security implication of calling a possibly untrusted code at compile time. But otherwise I think it is technically doable. But I would feel safer to prototype this first, to make sure.

I know Zig has comptime: What is Zig's Comptime? | Loris Cro's Personal Blog and I thought the compiler has a separate interpretation phase where it can interpret Zig’s code — but maybe I misunderstood. Either way, I want to avoid having to implement an interpreter, that feels like a lot of duplication. Fortunately it seems it should be possible to just call the already compiled function.

2 Likes