Skip to content

Commit

Permalink
[flang] Generate main only when a Fortran program statement is present (
Browse files Browse the repository at this point in the history
#89938)

This patch changes the behaviour for flang to only create and link to a
`main` entry point when the Fortran code has a program statement in it.

This means that flang-new can be used to link even when the program is
a mixed C/Fortran code with `main` present in C and no entry point
present in Fortran.

This also removes the `-fno-fortran-main` flag as this no longer has any
functionality.
  • Loading branch information
DavidTruby authored Apr 29, 2024
1 parent de6b2b9 commit 8d53866
Show file tree
Hide file tree
Showing 22 changed files with 287 additions and 226 deletions.
6 changes: 0 additions & 6 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -6589,12 +6589,6 @@ def J : JoinedOrSeparate<["-"], "J">,
Group<gfortran_Group>,
Alias<module_dir>;

let Visibility = [FlangOption] in {
def no_fortran_main : Flag<["-"], "fno-fortran-main">,
Visibility<[FlangOption]>, Group<f_Group>,
HelpText<"Do not include Fortran_main.a (provided by Flang) when linking">;
} // let Visibility = [ FlangOption ]

//===----------------------------------------------------------------------===//
// FC1 Options
//===----------------------------------------------------------------------===//
Expand Down
110 changes: 1 addition & 109 deletions clang/lib/Driver/ToolChains/CommonArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1191,118 +1191,10 @@ bool tools::addOpenMPRuntime(const Compilation &C, ArgStringList &CmdArgs,
return true;
}

/// Determines if --whole-archive is active in the list of arguments.
static bool isWholeArchivePresent(const ArgList &Args) {
bool WholeArchiveActive = false;
for (auto *Arg : Args.filtered(options::OPT_Wl_COMMA)) {
if (Arg) {
for (StringRef ArgValue : Arg->getValues()) {
if (ArgValue == "--whole-archive")
WholeArchiveActive = true;
if (ArgValue == "--no-whole-archive")
WholeArchiveActive = false;
}
}
}

return WholeArchiveActive;
}

/// Determine if driver is invoked to create a shared object library (-static)
static bool isSharedLinkage(const ArgList &Args) {
return Args.hasArg(options::OPT_shared);
}

/// Determine if driver is invoked to create a static object library (-shared)
static bool isStaticLinkage(const ArgList &Args) {
return Args.hasArg(options::OPT_static);
}

/// Add Fortran runtime libs for MSVC
static void addFortranRuntimeLibsMSVC(const ArgList &Args,
llvm::opt::ArgStringList &CmdArgs) {
unsigned RTOptionID = options::OPT__SLASH_MT;
if (auto *rtl = Args.getLastArg(options::OPT_fms_runtime_lib_EQ)) {
RTOptionID = llvm::StringSwitch<unsigned>(rtl->getValue())
.Case("static", options::OPT__SLASH_MT)
.Case("static_dbg", options::OPT__SLASH_MTd)
.Case("dll", options::OPT__SLASH_MD)
.Case("dll_dbg", options::OPT__SLASH_MDd)
.Default(options::OPT__SLASH_MT);
}
switch (RTOptionID) {
case options::OPT__SLASH_MT:
CmdArgs.push_back("/WHOLEARCHIVE:Fortran_main.static.lib");
break;
case options::OPT__SLASH_MTd:
CmdArgs.push_back("/WHOLEARCHIVE:Fortran_main.static_dbg.lib");
break;
case options::OPT__SLASH_MD:
CmdArgs.push_back("/WHOLEARCHIVE:Fortran_main.dynamic.lib");
break;
case options::OPT__SLASH_MDd:
CmdArgs.push_back("/WHOLEARCHIVE:Fortran_main.dynamic_dbg.lib");
break;
}
}

// Add FortranMain runtime lib
static void addFortranMain(const ToolChain &TC, const ArgList &Args,
llvm::opt::ArgStringList &CmdArgs) {
// 0. Shared-library linkage
// If we are attempting to link a library, we should not add
// -lFortran_main.a to the link line, as the `main` symbol is not
// required for a library and should also be provided by one of
// the translation units of the code that this shared library
// will be linked against eventually.
if (isSharedLinkage(Args) || isStaticLinkage(Args)) {
return;
}

// 1. MSVC
if (TC.getTriple().isKnownWindowsMSVCEnvironment()) {
addFortranRuntimeLibsMSVC(Args, CmdArgs);
return;
}

// 2. GNU and similar
const Driver &D = TC.getDriver();
const char *FortranMainLinkFlag = "-lFortran_main";

// Warn if the user added `-lFortran_main` - this library is an implementation
// detail of Flang and should be handled automaticaly by the driver.
for (const char *arg : CmdArgs) {
if (strncmp(arg, FortranMainLinkFlag, strlen(FortranMainLinkFlag)) == 0)
D.Diag(diag::warn_drv_deprecated_custom)
<< FortranMainLinkFlag
<< "see the Flang driver documentation for correct usage";
}

// The --whole-archive option needs to be part of the link line to make
// sure that the main() function from Fortran_main.a is pulled in by the
// linker. However, it shouldn't be used if it's already active.
// TODO: Find an equivalent of `--whole-archive` for Darwin and AIX.
if (!isWholeArchivePresent(Args) && !TC.getTriple().isMacOSX() &&
!TC.getTriple().isOSAIX()) {
CmdArgs.push_back("--whole-archive");
CmdArgs.push_back(FortranMainLinkFlag);
CmdArgs.push_back("--no-whole-archive");
return;
}

CmdArgs.push_back(FortranMainLinkFlag);
}

/// Add Fortran runtime libs
void tools::addFortranRuntimeLibs(const ToolChain &TC, const ArgList &Args,
llvm::opt::ArgStringList &CmdArgs) {
// 1. Link FortranMain
// FortranMain depends on FortranRuntime, so needs to be listed first. If
// -fno-fortran-main has been passed, skip linking Fortran_main.a
if (!Args.hasArg(options::OPT_no_fortran_main))
addFortranMain(TC, Args, CmdArgs);

// 2. Link FortranRuntime and FortranDecimal
// Link FortranRuntime and FortranDecimal
// These are handled earlier on Windows by telling the frontend driver to
// add the correct libraries to link against as dependents in the object
// file.
Expand Down
9 changes: 0 additions & 9 deletions clang/lib/Driver/ToolChains/Flang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,6 @@ static void processVSRuntimeLibrary(const ToolChain &TC, const ArgList &Args,
assert(TC.getTriple().isKnownWindowsMSVCEnvironment() &&
"can only add VS runtime library on Windows!");
// if -fno-fortran-main has been passed, skip linking Fortran_main.a
bool LinkFortranMain = !Args.hasArg(options::OPT_no_fortran_main);
if (TC.getTriple().isKnownWindowsMSVCEnvironment()) {
CmdArgs.push_back(Args.MakeArgString(
"--dependent-lib=" + TC.getCompilerRTBasename(Args, "builtins")));
Expand All @@ -300,26 +299,20 @@ static void processVSRuntimeLibrary(const ToolChain &TC, const ArgList &Args,
case options::OPT__SLASH_MT:
CmdArgs.push_back("-D_MT");
CmdArgs.push_back("--dependent-lib=libcmt");
if (LinkFortranMain)
CmdArgs.push_back("--dependent-lib=Fortran_main.static.lib");
CmdArgs.push_back("--dependent-lib=FortranRuntime.static.lib");
CmdArgs.push_back("--dependent-lib=FortranDecimal.static.lib");
break;
case options::OPT__SLASH_MTd:
CmdArgs.push_back("-D_MT");
CmdArgs.push_back("-D_DEBUG");
CmdArgs.push_back("--dependent-lib=libcmtd");
if (LinkFortranMain)
CmdArgs.push_back("--dependent-lib=Fortran_main.static_dbg.lib");
CmdArgs.push_back("--dependent-lib=FortranRuntime.static_dbg.lib");
CmdArgs.push_back("--dependent-lib=FortranDecimal.static_dbg.lib");
break;
case options::OPT__SLASH_MD:
CmdArgs.push_back("-D_MT");
CmdArgs.push_back("-D_DLL");
CmdArgs.push_back("--dependent-lib=msvcrt");
if (LinkFortranMain)
CmdArgs.push_back("--dependent-lib=Fortran_main.dynamic.lib");
CmdArgs.push_back("--dependent-lib=FortranRuntime.dynamic.lib");
CmdArgs.push_back("--dependent-lib=FortranDecimal.dynamic.lib");
break;
Expand All @@ -328,8 +321,6 @@ static void processVSRuntimeLibrary(const ToolChain &TC, const ArgList &Args,
CmdArgs.push_back("-D_DEBUG");
CmdArgs.push_back("-D_DLL");
CmdArgs.push_back("--dependent-lib=msvcrtd");
if (LinkFortranMain)
CmdArgs.push_back("--dependent-lib=Fortran_main.dynamic_dbg.lib");
CmdArgs.push_back("--dependent-lib=FortranRuntime.dynamic_dbg.lib");
CmdArgs.push_back("--dependent-lib=FortranDecimal.dynamic_dbg.lib");
break;
Expand Down
32 changes: 3 additions & 29 deletions flang/docs/FlangDriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,46 +179,20 @@ like this:

```
$ flang -v -o example example.o
"/usr/bin/ld" [...] example.o [...] "--whole-archive" "-lFortran_main"
"--no-whole-archive" "-lFortranRuntime" "-lFortranDecimal" [...]
"/usr/bin/ld" [...] example.o [...] "-lFortranRuntime" "-lFortranDecimal" [...]
```

The automatically added libraries are:

* `Fortran_main`: Provides the main entry point `main` that then invokes
`_QQmain` with the Fortran program unit. This library has a dependency to
the `FortranRuntime` library.
* `FortranRuntime`: Provides most of the Flang runtime library.
* `FortranDecimal`: Provides operations for decimal numbers.

The default is that, when using Flang as the linker, one of the Fortran
translation units provides the program unit and therefore it is assumed that
Fortran is the main code part (calling into C/C++ routines via `BIND (C)`
interfaces). When composing the linker commandline, Flang uses
`--whole-archive` and `--no-whole-archive` (Windows: `/WHOLEARCHIVE:`,
Darwin & AIX: *not implemented yet*) to make sure that all for `Fortran_main`
is processed by the linker. This is done to issue a proper error message when
multiple definitions of `main` occur. This happens, for instance, when linking
a code that has a Fortran program unit with a C/C++ code that also defines a
`main` function. A user may be required to explicitly provide the C++ runtime
libraries at link time (e.g., via `-lstdc++` for STL)

If the code is C/C++ based and invokes Fortran routines, one can either use Clang
or Flang as the linker driver. If Clang is used, it will automatically all
required runtime libraries needed by C++ (e.g., for STL) to the linker invocation.
In this case, one has to explicitly provide the Fortran runtime libraries
`FortranRuntime` and/or `FortranDecimal`. An alternative is to use Flang to link
and use the `-fno-fortran-main` flag. This flag removes
`Fortran_main` from the linker stage and hence requires one of the C/C++
translation units to provide a definition of the `main` function. In this case,
it may be required to explicitly supply C++ runtime libraries as mentioned above.

When creating shared or static libraries using Flang with `-shared` or `-static`
flag, Fortran_main is automatically removed from the linker stage (i.e.,
`-fno-fortran-main` is on by default). It is assumed that when creating a
static or shared library, the generated library does not need a `main`
function, as a final link stage will occur that will provide the `Fortran_main`
library when creating the final executable.
`FortranRuntime` and/or `FortranDecimal`. An alternative is to use Flang to link.
In this case, it may be required to explicitly supply C++ runtime libraries.

On Darwin, the logical root where the system libraries are located (sysroot)
must be specified. This can be done with the CMake build flag `DEFAULT_SYSROOT`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

namespace fir {
class FirOpBuilder;
class GlobalOp;
} // namespace fir

namespace mlir {
Expand All @@ -37,7 +38,7 @@ namespace fir::runtime {
/// Create the list of environment variable defaults for the runtime to set. The
/// form of the generated list is defined in the runtime header file
/// environment-default-list.h
void genEnvironmentDefaults(
fir::GlobalOp genEnvironmentDefaults(
fir::FirOpBuilder &builder, mlir::Location loc,
const std::vector<Fortran::lower::EnvironmentDefault> &envDefaults);

Expand Down
28 changes: 28 additions & 0 deletions flang/include/flang/Optimizer/Builder/Runtime/Main.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//===-- Main.h - generate main runtime API calls ----------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef FORTRAN_OPTIMIZER_BUILDER_RUNTIME_MAIN_H
#define FORTRAN_OPTIMIZER_BUILDER_RUNTIME_MAIN_H

namespace mlir {
class Location;
} // namespace mlir

namespace fir {
class FirOpBuilder;
class GlobalOp;
} // namespace fir

namespace fir::runtime {

void genMain(fir::FirOpBuilder &builder, mlir::Location loc,
fir::GlobalOp &env);

}

#endif // FORTRAN_OPTIMIZER_BUILDER_RUNTIME_MAIN_H
7 changes: 5 additions & 2 deletions flang/lib/Lower/Bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "flang/Optimizer/Builder/Runtime/Character.h"
#include "flang/Optimizer/Builder/Runtime/Derived.h"
#include "flang/Optimizer/Builder/Runtime/EnvironmentDefaults.h"
#include "flang/Optimizer/Builder/Runtime/Main.h"
#include "flang/Optimizer/Builder/Runtime/Ragged.h"
#include "flang/Optimizer/Builder/Runtime/Stop.h"
#include "flang/Optimizer/Builder/Todo.h"
Expand Down Expand Up @@ -359,8 +360,10 @@ class FirConverter : public Fortran::lower::AbstractConverter {
// not need to be generated even if no defaults are specified.
// However, generating main or changing when the runtime reads
// environment variables is required to do so.
fir::runtime::genEnvironmentDefaults(*builder, toLocation(),
bridge.getEnvironmentDefaults());
auto env = fir::runtime::genEnvironmentDefaults(
*builder, toLocation(), bridge.getEnvironmentDefaults());

fir::runtime::genMain(*builder, toLocation(), env);
});

finalizeOpenACCLowering();
Expand Down
1 change: 1 addition & 0 deletions flang/lib/Optimizer/Builder/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ add_flang_library(FIRBuilder
Runtime/Execute.cpp
Runtime/Inquiry.cpp
Runtime/Intrinsics.cpp
Runtime/Main.cpp
Runtime/Numeric.cpp
Runtime/Pointer.cpp
Runtime/Ragged.cpp
Expand Down
7 changes: 3 additions & 4 deletions flang/lib/Optimizer/Builder/Runtime/EnvironmentDefaults.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#include "flang/Optimizer/Support/InternalNames.h"
#include "llvm/ADT/ArrayRef.h"

void fir::runtime::genEnvironmentDefaults(
fir::GlobalOp fir::runtime::genEnvironmentDefaults(
fir::FirOpBuilder &builder, mlir::Location loc,
const std::vector<Fortran::lower::EnvironmentDefault> &envDefaults) {
std::string envDefaultListPtrName =
Expand All @@ -34,14 +34,13 @@ void fir::runtime::genEnvironmentDefaults(

// If no defaults were specified, initialize with a null pointer.
if (envDefaults.empty()) {
builder.createGlobalConstant(
return builder.createGlobalConstant(
loc, envDefaultListRefTy, envDefaultListPtrName,
[&](fir::FirOpBuilder &builder) {
mlir::Value nullVal =
builder.createNullConstant(loc, envDefaultListRefTy);
builder.create<fir::HasValueOp>(loc, nullVal);
});
return;
}

// Create the Item list.
Expand Down Expand Up @@ -99,7 +98,7 @@ void fir::runtime::genEnvironmentDefaults(
envDefaultListBuilder, linkOnce);

// Define the pointer to the list used by the runtime.
builder.createGlobalConstant(
return builder.createGlobalConstant(
loc, envDefaultListRefTy, envDefaultListPtrName,
[&](fir::FirOpBuilder &builder) {
mlir::Value addr = builder.create<fir::AddrOfOp>(
Expand Down
62 changes: 62 additions & 0 deletions flang/lib/Optimizer/Builder/Runtime/Main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//===-- Main.cpp - generate main runtime API calls --------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "flang/Optimizer/Builder/Runtime/Main.h"
#include "flang/Optimizer/Builder/BoxValue.h"
#include "flang/Optimizer/Builder/FIRBuilder.h"
#include "flang/Optimizer/Builder/Runtime/RTBuilder.h"
#include "flang/Optimizer/Dialect/FIROps.h"
#include "flang/Optimizer/Dialect/FIRType.h"
#include "flang/Runtime/main.h"
#include "flang/Runtime/stop.h"

using namespace Fortran::runtime;

/// Create a `int main(...)` that calls the Fortran entry point
void fir::runtime::genMain(fir::FirOpBuilder &builder, mlir::Location loc,
fir::GlobalOp &env) {
auto *context = builder.getContext();
auto argcTy = builder.getDefaultIntegerType();
auto ptrTy = mlir::LLVM::LLVMPointerType::get(context);

// void ProgramStart(int argc, char** argv, char** envp,
// _QQEnvironmentDefaults* env)
auto startFn = builder.createFunction(
loc, RTNAME_STRING(ProgramStart),
mlir::FunctionType::get(context, {argcTy, ptrTy, ptrTy, ptrTy}, {}));
// void ProgramStop()
auto stopFn =
builder.createFunction(loc, RTNAME_STRING(ProgramEndStatement),
mlir::FunctionType::get(context, {}, {}));

// int main(int argc, char** argv, char** envp)
auto mainFn = builder.createFunction(
loc, "main",
mlir::FunctionType::get(context, {argcTy, ptrTy, ptrTy}, argcTy));
// void _QQmain()
auto qqMainFn = builder.createFunction(
loc, "_QQmain", mlir::FunctionType::get(context, {}, {}));

mainFn.setPublic();

auto *block = mainFn.addEntryBlock();
mlir::OpBuilder::InsertionGuard insertGuard(builder);
builder.setInsertionPointToStart(block);

llvm::SmallVector<mlir::Value, 4> args(block->getArguments());
auto envAddr =
builder.create<fir::AddrOfOp>(loc, env.getType(), env.getSymbol());
args.push_back(envAddr);

builder.create<fir::CallOp>(loc, startFn, args);
builder.create<fir::CallOp>(loc, qqMainFn);
builder.create<fir::CallOp>(loc, stopFn);

mlir::Value ret = builder.createIntegerConstant(loc, argcTy, 0);
builder.create<mlir::func::ReturnOp>(loc, ret);
}
Loading

0 comments on commit 8d53866

Please sign in to comment.