[RFC] Link to Fortran_main using embedded linker flags

Background:
Currently on non-Windows platforms the compiler driver adds Fortran_main on the link line when linking with flang-new. This can cause issues when linking with flang-new as the linker but when main is already defined in another language in a multi-language project. On Windows we use embedded linker directives to link to all the runtimes, but we embed them in every object file causing equivalent behaviour.

Proposal:
To get around this issue, we should switch to adding an embedded linker directive to the object file for the translation unit that contained the program statement, and only that translation unit. This can be done using the llvm.linker_options MLIR operation (which lowers to the equivalent LLVM IR already). This is supported in all the object file formats that I am aware flang-new supports; namely ELF, PE and MachO.
We could additionally add the other Fortran runtimes similarly to every Fortran object file on Linux and MacOS as well so that linking those files with clang or ld still pulls in the runtimes, but that is a separate issue.
As far as I am aware, this is how Objective C pulls in its runtime, so there is precedent for other languages doing things this way.

Does anyone have any thoughts on this as a way to resolve the main linkage issues?

1 Like

The other way that I could think of would be to go the same direction that GFortran picked and emit the main stub in all translation units that have a program unit.

Would the suggested solution with an embedded linker directive trigger linker errors when multiple program units are being linked as well as the mixed language case, when both program unit in Fortran and main function from C/C++ are defined?

If we can catch all the usual errors with the suggest approach, I’ll be fine with it!

Would -fno-fortran-main still work?

I think yes. It would then not embed the linker directive.

With the generated main symbol, it would turn off the code-gen for that.

If we use the same linker command line option we use now as an embedded linker directive, then nothing should change with regards to behaviour of linking multiple program units.

We can also still enable -fno-fortran-main by just not emitting the linker directive even if we see a program unit. Of course linking will most likely then fail anyway because nothing will call the Fortran entry point but the user has asked for that behaviour at that point I guess?

I am not sure, it would still work. -fno-fortran-main would have to be provided at compile time instead of link time. That or there would have to be a mechanism to ignore some embedded directives.

Good point. I’m not sure the flag should need to work anymore though… Is there a use case for -fno-fortran-main when there actually is a Fortran program statement that I am missing?

At NVIDIA, we use it for mixed language SPEC benchmarks. I am aware of wrf and cam for instance. I have run into the issue enough that this RFC caught my eye. Classic flang has -Mnomain, ifort has -nofor-main. Of course we could modify makefiles to link with the C compiler but then you would have to explicitly list the fortran runtime libraries. The use case is out there. How prevalent and who might be impacted, I would not be able to tell.

XLF ( I think gfortran as well) generates

define dso_local signext i32 @main() #0 {

When users have main in their Fortran program. It is inside of the object files that contains Fortran main program.
It doesn’t link to an external library that has main, so when Fortran code does not have main, it will not have the above code in the object file.

In the case that the main is in C and the C code calls a Fortran procedure, XLF was able to link it because there is no externally linked in “fake” Fortran main.

I understand “-fno-fortran-main” is for this usage. It is just the existing users will need to add it to their link step.

In the way I’m proposing we link, the external Fortran_main will only be linked in the case where one of the object files came from a translation unit containing a Fortran program statement (the Fortran equivalent of the main entry point). In this case the end result is more like what xlf is doing, albeit achieving that a different way.

I understand that the -fno-fortran-main option is useful when linking a mixed language program where main is in C for example; in that case it wouldn’t be necessary with the new approach as long as the Fortran doesn’t contain a program statement. What I’m wondering is if there’s a reason we might want it even if a Fortran TU does specify an entry point (ie using the program statement). Given that xlf seems to do something similar to the proposed change without a flag to disable it I’d assume that there isn’t?

As a more concrete example, WRF wouldn’t need the flag in this scenario even when linking with flang-new as none of the Fortran files declare a program statement and so none of the object files would contain the linker directive.

Right. I have been looking for some use cases and I am seeing that we are using -fnomain in places where it is not needed. I have asked internally for some additional cases.

@szakharin pointed out that flang-new links with --whole-archive -lFortran_main --no-whole-archive . Any ideas why? Without --whole-archive, flang-new can link with a main in C.

Without --whole-archive the C main always gets linked, and Fortran_main gets thrown away. It was added to provide an error to the user if main is defined in both C and Fortran.

How can I reproduce this behavior?

Without the FortranMain.a, I observe this:

"/usr/bin/ld" "-z" "relro" "--hash-style=gnu" "--eh-frame-hdr" "-m" "elf_x86_64" "-pie" "-dynamic-linker" "/lib64/ld-linux-x86-64.so.2" "-o" "a.out" "/lib/x86_64-linux-gnu/Scrt1.o" "/lib/x86_64-linux-gnu/crti.o" "/usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o" "-L/proj/nv/llvm/Linux_x86_64/llvm-2675/bin/../lib/x86_64-unknown-linux-gnu" "-L/usr/lib/gcc/x86_64-linux-gnu/9" "-L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib64" "-L/lib/x86_64-linux-gnu" "-L/lib/../lib64" "-L/usr/lib/x86_64-linux-gnu" "-L/usr/lib/../lib64" "-L/lib" "-L/usr/lib" "prog.o" "-L/proj/nv/llvm/Linux_x86_64/llvm-2675/lib" "-lFortranFloat128Math" "--as-needed" "-lquadmath" "--no-as-needed" "-lFortranRuntime" "-lFortranDecimal" "-lm" "-lgcc" "--as-needed" "-lgcc_s" "--no-as-needed" "-lc" "-lgcc" "--as-needed" "-lgcc_s" "--no-as-needed" "/usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o" "/lib/x86_64-linux-gnu/crtn.o"
/usr/bin/ld: /lib/x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x24): undefined reference to `main'

Which behaviour are you trying to reproduce?

In case it wasn’t clear in my previous reply I meant that with --whole-archive on the link line you force link Fortran_main (and so error if main exists twice), which is where we currently add it.

I mostly wanted you to be aware there is -fno-fortran-main available as a low tech solution for this problem. Digging and asking around, we use it in the case main is in C such as the mixed language SPEC benchmarks. (We do not have cases when main is in C and Fortran as a PROGRAM statement).

So, your proposal is really about removing -fno-fortran-main from the command line and automating it. I do not see a reason to object to it.

It should be ok to always error when main is in C and there is a PROGRAM statement.

Unfortunately I tried to implement this and realised that my prior assumptions were wrong: ELF doesn’t support embedded linker flags. The llvm.linker.options metadata does emit the linker flags into the object file but they do not appear to be used by either GNU ld or lld.

Another idea that I had that would have the same effect at least for Fortran_main though would be to move the main function into the FortranRuntime library under a different name (_FortranEntry or something) and emit an IR fragment into the object file containing the entry point that simply defines a function called main and calls the _FortranEntry function from it. That would mean not having to maintain the full Fortran entry point in IR but still only emitting it when there is a program statement. This would be similar to the XLF behaviour described above.

1 Like

I posted a patch for this on github: [flang] Generate main only when a Fortran program statement is present by DavidTruby · Pull Request #89938 · llvm/llvm-project · GitHub