Skip to content

Reland "[flang] add option to generate runtime type info as external (#145901) " #146071

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions flang/include/flang/Evaluate/tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -1585,6 +1585,7 @@ bool IsExtensibleType(const DerivedTypeSpec *);
bool IsSequenceOrBindCType(const DerivedTypeSpec *);
bool IsBuiltinDerivedType(const DerivedTypeSpec *derived, const char *name);
bool IsBuiltinCPtr(const Symbol &);
bool IsFromBuiltinModule(const Symbol &);
bool IsEventType(const DerivedTypeSpec *);
bool IsLockType(const DerivedTypeSpec *);
bool IsNotifyType(const DerivedTypeSpec *);
Expand Down
4 changes: 4 additions & 0 deletions flang/include/flang/Lower/LoweringOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,9 @@ ENUM_LOWERINGOPT(RepackArraysWhole, unsigned, 1, 0)
/// If true, CUDA Fortran runtime check is inserted.
ENUM_LOWERINGOPT(CUDARuntimeCheck, unsigned, 1, 0)

/// If true, do not generate definition for runtime type info global objects of
/// derived types defined in other compilation units.
ENUM_LOWERINGOPT(SkipExternalRttiDefinition, unsigned, 1, 0)

#undef LOWERINGOPT
#undef ENUM_LOWERINGOPT
3 changes: 3 additions & 0 deletions flang/include/flang/Optimizer/CodeGen/CodeGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ struct FIRToLLVMPassOptions {
// that such programs would crash at runtime if the derived type descriptors
// are required by the runtime, so this is only an option to help debugging.
bool ignoreMissingTypeDescriptors = false;
// Similar to ignoreMissingTypeDescriptors, but generate external declaration
// for the missing type descriptor globals instead.
bool skipExternalRttiDefinition = false;

// Generate TBAA information for FIR types and memory accessing operations.
bool applyTBAA = false;
Expand Down
13 changes: 13 additions & 0 deletions flang/include/flang/Optimizer/Passes/CommandLineOpts.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ extern llvm::cl::opt<std::size_t> arrayStackAllocationThreshold;
/// generated by the frontend.
extern llvm::cl::opt<bool> ignoreMissingTypeDescriptors;

/// Shared option in tools to only generate rtti static object definitions for
/// derived types defined in the current compilation unit. Derived type
/// descriptor object for types defined in other objects will only be declared
/// as external. This also changes the linkage of rtti objects defined in the
/// current compilation unit from linkonce_odr to external so that unused rtti
/// objects are retained and can be accessed from other compilation units. This
/// is an experimental option to explore compilation speed improvements and is
/// an ABI breaking change because of the linkage change.
/// It will also require linking against module file objects of modules defining
/// only types (even for trivial types without type bound procedures, which
/// differs from most compilers).
extern llvm::cl::opt<bool> skipExternalRttiDefinition;

/// Default optimization level used to create Flang pass pipeline is O0.
extern llvm::OptimizationLevel defaultOptLevel;

Expand Down
26 changes: 0 additions & 26 deletions flang/include/flang/Optimizer/Support/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,6 @@ inline std::int64_t toInt(mlir::arith::ConstantOp cop) {
.getSExtValue();
}

// Reconstruct binding tables for dynamic dispatch.
using BindingTable = llvm::DenseMap<llvm::StringRef, unsigned>;
using BindingTables = llvm::DenseMap<llvm::StringRef, BindingTable>;

inline void buildBindingTables(BindingTables &bindingTables,
mlir::ModuleOp mod) {

// The binding tables are defined in FIR after lowering inside fir.type_info
// operations. Go through each binding tables and store the procedure name and
// binding index for later use by the fir.dispatch conversion pattern.
for (auto typeInfo : mod.getOps<fir::TypeInfoOp>()) {
unsigned bindingIdx = 0;
BindingTable bindings;
if (typeInfo.getDispatchTable().empty()) {
bindingTables[typeInfo.getSymName()] = bindings;
continue;
}
for (auto dtEntry :
typeInfo.getDispatchTable().front().getOps<fir::DTEntryOp>()) {
bindings[dtEntry.getMethod()] = bindingIdx;
++bindingIdx;
}
bindingTables[typeInfo.getSymName()] = bindings;
}
}

// Translate front-end KINDs for use in the IR and code gen.
inline std::vector<fir::KindTy>
fromDefaultKinds(const Fortran::common::IntrinsicTypeDefaultKinds &defKinds) {
Expand Down
4 changes: 4 additions & 0 deletions flang/include/flang/Semantics/runtime-type-info.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ RuntimeDerivedTypeTables BuildRuntimeDerivedTypeTables(SemanticsContext &);
/// to describe other derived types at runtime in flang descriptor.
constexpr char typeInfoBuiltinModule[]{"__fortran_type_info"};

/// Name of the builtin derived type in __fortran_type_inf that is used for
/// derived type descriptors.
constexpr char typeDescriptorTypeName[]{"derivedtype"};

/// Name of the bindings descriptor component in the DerivedType type of the
/// __Fortran_type_info module
constexpr char bindingDescCompName[]{"binding"};
Expand Down
5 changes: 5 additions & 0 deletions flang/lib/Evaluate/tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2334,6 +2334,11 @@ bool IsBuiltinCPtr(const Symbol &symbol) {
return false;
}

bool IsFromBuiltinModule(const Symbol &symbol) {
const Scope &scope{symbol.GetUltimate().owner()};
return IsSameModule(&scope, scope.context().GetBuiltinsScope());
}

bool IsIsoCType(const DerivedTypeSpec *derived) {
return IsBuiltinDerivedType(derived, "c_ptr") ||
IsBuiltinDerivedType(derived, "c_funptr");
Expand Down
2 changes: 2 additions & 0 deletions flang/lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "flang/Frontend/CodeGenOptions.h"
#include "flang/Frontend/PreprocessorOptions.h"
#include "flang/Frontend/TargetOptions.h"
#include "flang/Optimizer/Passes/CommandLineOpts.h"
#include "flang/Semantics/semantics.h"
#include "flang/Support/Fortran-features.h"
#include "flang/Support/OpenMP-features.h"
Expand Down Expand Up @@ -1792,6 +1793,7 @@ void CompilerInvocation::setLoweringOptions() {
// Lower TRANSPOSE as a runtime call under -O0.
loweringOpts.setOptimizeTranspose(codegenOpts.OptimizationLevel > 0);
loweringOpts.setUnderscoring(codegenOpts.Underscoring);
loweringOpts.setSkipExternalRttiDefinition(skipExternalRttiDefinition);

const Fortran::common::LangOptions &langOptions = getLangOpts();
loweringOpts.setIntegerWrapAround(langOptions.getSignedOverflowBehavior() ==
Expand Down
15 changes: 14 additions & 1 deletion flang/lib/Lower/Bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ class TypeInfoConverter {
}

void createTypeInfo(Fortran::lower::AbstractConverter &converter) {
createTypeInfoForTypeDescriptorBuiltinType(converter);
while (!registeredTypeInfoA.empty()) {
currentTypeInfoStack = &registeredTypeInfoB;
for (const TypeInfo &info : registeredTypeInfoA)
Expand All @@ -277,10 +278,22 @@ class TypeInfoConverter {
private:
void createTypeInfoOpAndGlobal(Fortran::lower::AbstractConverter &converter,
const TypeInfo &info) {
Fortran::lower::createRuntimeTypeInfoGlobal(converter, info.symbol.get());
if (!converter.getLoweringOptions().getSkipExternalRttiDefinition())
Fortran::lower::createRuntimeTypeInfoGlobal(converter, info.symbol.get());
createTypeInfoOp(converter, info);
}

void createTypeInfoForTypeDescriptorBuiltinType(
Fortran::lower::AbstractConverter &converter) {
if (registeredTypeInfoA.empty())
return;
auto builtinTypeInfoType = llvm::cast<fir::RecordType>(
converter.genType(registeredTypeInfoA[0].symbol.get()));
converter.getFirOpBuilder().createTypeInfoOp(
registeredTypeInfoA[0].loc, builtinTypeInfoType,
/*parentType=*/fir::RecordType{});
}

void createTypeInfoOp(Fortran::lower::AbstractConverter &converter,
const TypeInfo &info) {
fir::RecordType parentType{};
Expand Down
22 changes: 13 additions & 9 deletions flang/lib/Lower/ConvertVariable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -647,13 +647,19 @@ fir::GlobalOp Fortran::lower::defineGlobal(

/// Return linkage attribute for \p var.
static mlir::StringAttr
getLinkageAttribute(fir::FirOpBuilder &builder,
getLinkageAttribute(Fortran::lower::AbstractConverter &converter,
const Fortran::lower::pft::Variable &var) {
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
// Runtime type info for a same derived type is identical in each compilation
// unit. It desired to avoid having to link against module that only define a
// type. Therefore the runtime type info is generated everywhere it is needed
// with `linkonce_odr` LLVM linkage.
if (var.isRuntimeTypeInfoData())
// with `linkonce_odr` LLVM linkage (unless the skipExternalRttiDefinition
// option is set, in which case one will need to link against objects of
// modules defining types). Builtin objects rtti is always generated because
// the builtin module is currently not compiled or part of the runtime.
if (var.isRuntimeTypeInfoData() &&
(!converter.getLoweringOptions().getSkipExternalRttiDefinition() ||
Fortran::semantics::IsFromBuiltinModule(var.getSymbol())))
return builder.createLinkOnceODRLinkage();
if (var.isModuleOrSubmoduleVariable())
return {}; // external linkage
Expand All @@ -673,7 +679,7 @@ static void instantiateGlobal(Fortran::lower::AbstractConverter &converter,
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
std::string globalName = converter.mangleName(sym);
mlir::Location loc = genLocation(converter, sym);
mlir::StringAttr linkage = getLinkageAttribute(builder, var);
mlir::StringAttr linkage = getLinkageAttribute(converter, var);
fir::GlobalOp global;
if (var.isModuleOrSubmoduleVariable()) {
// A non-intrinsic module global is defined when lowering the module.
Expand Down Expand Up @@ -1265,7 +1271,7 @@ instantiateAggregateStore(Fortran::lower::AbstractConverter &converter,
if (var.isGlobal()) {
fir::GlobalOp global;
auto &aggregate = var.getAggregateStore();
mlir::StringAttr linkage = getLinkageAttribute(builder, var);
mlir::StringAttr linkage = getLinkageAttribute(converter, var);
if (var.isModuleOrSubmoduleVariable()) {
// A module global was or will be defined when lowering the module. Emit
// only a declaration if the global does not exist at that point.
Expand Down Expand Up @@ -2470,8 +2476,7 @@ void Fortran::lower::defineModuleVariable(
AbstractConverter &converter, const Fortran::lower::pft::Variable &var) {
// Use empty linkage for module variables, which makes them available
// for use in another unit.
mlir::StringAttr linkage =
getLinkageAttribute(converter.getFirOpBuilder(), var);
mlir::StringAttr linkage = getLinkageAttribute(converter, var);
if (!var.isGlobal())
fir::emitFatalError(converter.getCurrentLocation(),
"attempting to lower module variable as local");
Expand Down Expand Up @@ -2606,10 +2611,9 @@ void Fortran::lower::createIntrinsicModuleGlobal(
void Fortran::lower::createRuntimeTypeInfoGlobal(
Fortran::lower::AbstractConverter &converter,
const Fortran::semantics::Symbol &typeInfoSym) {
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
std::string globalName = converter.mangleName(typeInfoSym);
auto var = Fortran::lower::pft::Variable(typeInfoSym, /*global=*/true);
mlir::StringAttr linkage = getLinkageAttribute(builder, var);
mlir::StringAttr linkage = getLinkageAttribute(converter, var);
defineGlobal(converter, var, globalName, linkage);
}

Expand Down
102 changes: 53 additions & 49 deletions flang/lib/Optimizer/CodeGen/CodeGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,51 @@ genCUFAllocDescriptor(mlir::Location loc,
.getResult();
}

/// Get the address of the type descriptor global variable that was created by
/// lowering for derived type \p recType.
template <typename ModOpTy>
static mlir::Value
getTypeDescriptor(ModOpTy mod, mlir::ConversionPatternRewriter &rewriter,
mlir::Location loc, fir::RecordType recType,
const fir::FIRToLLVMPassOptions &options) {
std::string name =
options.typeDescriptorsRenamedForAssembly
? fir::NameUniquer::getTypeDescriptorAssemblyName(recType.getName())
: fir::NameUniquer::getTypeDescriptorName(recType.getName());
mlir::Type llvmPtrTy = ::getLlvmPtrType(mod.getContext());
if (auto global = mod.template lookupSymbol<fir::GlobalOp>(name))
return rewriter.create<mlir::LLVM::AddressOfOp>(loc, llvmPtrTy,
global.getSymName());
// The global may have already been translated to LLVM.
if (auto global = mod.template lookupSymbol<mlir::LLVM::GlobalOp>(name))
return rewriter.create<mlir::LLVM::AddressOfOp>(loc, llvmPtrTy,
global.getSymName());
// Type info derived types do not have type descriptors since they are the
// types defining type descriptors.
if (options.ignoreMissingTypeDescriptors ||
fir::NameUniquer::belongsToModule(
name, Fortran::semantics::typeInfoBuiltinModule))
return rewriter.create<mlir::LLVM::ZeroOp>(loc, llvmPtrTy);

if (!options.skipExternalRttiDefinition)
fir::emitFatalError(loc,
"runtime derived type info descriptor was not "
"generated and skipExternalRttiDefinition and "
"ignoreMissingTypeDescriptors options are not set");

// Rtti for a derived type defined in another compilation unit and for which
// rtti was not defined in lowering because of the skipExternalRttiDefinition
// option. Generate the object declaration now.
auto insertPt = rewriter.saveInsertionPoint();
rewriter.setInsertionPoint(mod.getBody(), mod.getBody()->end());
mlir::LLVM::GlobalOp global = rewriter.create<mlir::LLVM::GlobalOp>(
loc, llvmPtrTy, /*constant=*/true, mlir::LLVM::Linkage::External, name,
mlir::Attribute());
rewriter.restoreInsertionPoint(insertPt);
return rewriter.create<mlir::LLVM::AddressOfOp>(loc, llvmPtrTy,
global.getSymName());
}

/// Common base class for embox to descriptor conversion.
template <typename OP>
struct EmboxCommonConversion : public fir::FIROpConversion<OP> {
Expand Down Expand Up @@ -1406,36 +1451,6 @@ struct EmboxCommonConversion : public fir::FIROpConversion<OP> {
stride);
}

/// Get the address of the type descriptor global variable that was created by
/// lowering for derived type \p recType.
template <typename ModOpTy>
mlir::Value
getTypeDescriptor(ModOpTy mod, mlir::ConversionPatternRewriter &rewriter,
mlir::Location loc, fir::RecordType recType) const {
std::string name =
this->options.typeDescriptorsRenamedForAssembly
? fir::NameUniquer::getTypeDescriptorAssemblyName(recType.getName())
: fir::NameUniquer::getTypeDescriptorName(recType.getName());
mlir::Type llvmPtrTy = ::getLlvmPtrType(mod.getContext());
if (auto global = mod.template lookupSymbol<fir::GlobalOp>(name)) {
return rewriter.create<mlir::LLVM::AddressOfOp>(loc, llvmPtrTy,
global.getSymName());
}
if (auto global = mod.template lookupSymbol<mlir::LLVM::GlobalOp>(name)) {
// The global may have already been translated to LLVM.
return rewriter.create<mlir::LLVM::AddressOfOp>(loc, llvmPtrTy,
global.getSymName());
}
// Type info derived types do not have type descriptors since they are the
// types defining type descriptors.
if (!this->options.ignoreMissingTypeDescriptors &&
!fir::NameUniquer::belongsToModule(
name, Fortran::semantics::typeInfoBuiltinModule))
fir::emitFatalError(
loc, "runtime derived type info descriptor was not generated");
return rewriter.create<mlir::LLVM::ZeroOp>(loc, llvmPtrTy);
}

template <typename ModOpTy>
mlir::Value populateDescriptor(mlir::Location loc, ModOpTy mod,
fir::BaseBoxType boxTy, mlir::Type inputType,
Expand Down Expand Up @@ -1500,16 +1515,17 @@ struct EmboxCommonConversion : public fir::FIROpConversion<OP> {
mlir::Type innerType = fir::unwrapInnerType(inputType);
if (innerType && mlir::isa<fir::RecordType>(innerType)) {
auto recTy = mlir::dyn_cast<fir::RecordType>(innerType);
typeDesc = getTypeDescriptor(mod, rewriter, loc, recTy);
typeDesc =
getTypeDescriptor(mod, rewriter, loc, recTy, this->options);
} else {
// Unlimited polymorphic type descriptor with no record type. Set
// type descriptor address to a clean state.
typeDesc = rewriter.create<mlir::LLVM::ZeroOp>(
loc, ::getLlvmPtrType(mod.getContext()));
}
} else {
typeDesc = getTypeDescriptor(mod, rewriter, loc,
fir::unwrapIfDerived(boxTy));
typeDesc = getTypeDescriptor(
mod, rewriter, loc, fir::unwrapIfDerived(boxTy), this->options);
}
}
if (typeDesc)
Expand Down Expand Up @@ -3021,22 +3037,10 @@ struct TypeDescOpConversion : public fir::FIROpConversion<fir::TypeDescOp> {
assert(mlir::isa<fir::RecordType>(inTy) && "expecting fir.type");
auto recordType = mlir::dyn_cast<fir::RecordType>(inTy);
auto module = typeDescOp.getOperation()->getParentOfType<mlir::ModuleOp>();
std::string typeDescName =
this->options.typeDescriptorsRenamedForAssembly
? fir::NameUniquer::getTypeDescriptorAssemblyName(
recordType.getName())
: fir::NameUniquer::getTypeDescriptorName(recordType.getName());
auto llvmPtrTy = ::getLlvmPtrType(typeDescOp.getContext());
if (auto global = module.lookupSymbol<mlir::LLVM::GlobalOp>(typeDescName)) {
rewriter.replaceOpWithNewOp<mlir::LLVM::AddressOfOp>(
typeDescOp, llvmPtrTy, global.getSymName());
return mlir::success();
} else if (auto global = module.lookupSymbol<fir::GlobalOp>(typeDescName)) {
rewriter.replaceOpWithNewOp<mlir::LLVM::AddressOfOp>(
typeDescOp, llvmPtrTy, global.getSymName());
return mlir::success();
}
return mlir::failure();
mlir::Value typeDesc = getTypeDescriptor(
module, rewriter, typeDescOp.getLoc(), recordType, this->options);
rewriter.replaceOp(typeDescOp, typeDesc);
return mlir::success();
}
};

Expand Down
6 changes: 6 additions & 0 deletions flang/lib/Optimizer/Passes/CommandLineOpts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ cl::opt<bool> ignoreMissingTypeDescriptors(
"translating FIR to LLVM"),
cl::init(false), cl::Hidden);

cl::opt<bool> skipExternalRttiDefinition(
"skip-external-rtti-definition", llvm::cl::init(false),
llvm::cl::desc("do not define rtti static objects for types belonging to "
"other compilation units"),
cl::Hidden);

OptimizationLevel defaultOptLevel{OptimizationLevel::O0};

codegenoptions::DebugInfoKind noDebugInfo{codegenoptions::NoDebugInfo};
Expand Down
1 change: 1 addition & 0 deletions flang/lib/Optimizer/Passes/Pipelines.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ void addFIRToLLVMPass(mlir::PassManager &pm,
const MLIRToLLVMPassPipelineConfig &config) {
fir::FIRToLLVMPassOptions options;
options.ignoreMissingTypeDescriptors = ignoreMissingTypeDescriptors;
options.skipExternalRttiDefinition = skipExternalRttiDefinition;
options.applyTBAA = config.AliasAnalysis;
options.forceUnifiedTBAATree = useOldAliasTags;
options.typeDescriptorsRenamedForAssembly =
Expand Down
Loading