Skip to content
Open
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
7 changes: 6 additions & 1 deletion clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -7113,7 +7113,12 @@ defm dump_parse_tree : BooleanFFlag<"dump-parse-tree">, Group<gfortran_Group>;
defm external_blas : BooleanFFlag<"external-blas">, Group<gfortran_Group>;
defm f2c : BooleanFFlag<"f2c">, Group<gfortran_Group>;
defm frontend_optimize : BooleanFFlag<"frontend-optimize">, Group<gfortran_Group>;
defm init_local_zero : BooleanFFlag<"init-local-zero">, Group<gfortran_Group>;
defm init_local_zero
: BooleanFFlag<"init-local-zero">,
Group<gfortran_Group>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that the gfortran_Group was intended for options that were supported in gfortran but not handled by flang. I don't think this is necessary any longer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the review. I am currently reworking the lowering portion of the PR; I will address these changes in the next revision of the PR.

Visibility<[FlangOption, FC1Option]>,
HelpText<"Initialize real/integer/character/logical/complex type "
"to zero.">;
Comment on lines +7120 to +7121
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
HelpText<"Initialize real/integer/character/logical/complex type "
"to zero.">;
HelpText<"Initialize real, integer, character, logical and complex variables "
"to zero.">;

defm integer_4_integer_8 : BooleanFFlag<"integer-4-integer-8">, Group<gfortran_Group>;
defm max_identifier_length : BooleanFFlag<"max-identifier-length">, Group<gfortran_Group>;
defm module_private : BooleanFFlag<"module-private">, Group<gfortran_Group>;
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Driver/ToolChains/Flang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ void Flang::addCodegenOptions(const ArgList &Args,
options::OPT_fno_ppc_native_vec_elem_order,
options::OPT_fppc_native_vec_elem_order, options::OPT_finit_global_zero,
options::OPT_fno_init_global_zero, options::OPT_frepack_arrays,
options::OPT_fno_repack_arrays,
options::OPT_fno_repack_arrays, options::OPT_finit_local_zero,
options::OPT_frepack_arrays_contiguity_EQ,
options::OPT_fstack_repack_arrays, options::OPT_fno_stack_repack_arrays,
options::OPT_ftime_report, options::OPT_ftime_report_EQ,
Expand Down
3 changes: 3 additions & 0 deletions flang/include/flang/Lower/LoweringOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,8 @@ ENUM_LOWERINGOPT(ComplexDivisionToRuntime, unsigned, 1, 1)
/// of the lowering pipeline.
ENUM_LOWERINGOPT(RegisterMLIRDiagnosticsHandler, unsigned, 1, 1)

/// When true, it enables semantics for -finit-local-zero during codegen.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
/// When true, it enables semantics for -finit-local-zero during codegen.
/// When true, enables semantics for -finit-local-zero during codegen.

ENUM_LOWERINGOPT(InitLocalZeroDef, unsigned, 1, 0)

#undef LOWERINGOPT
#undef ENUM_LOWERINGOPT
5 changes: 5 additions & 0 deletions flang/lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,11 @@ bool CompilerInvocation::createFromArgs(
else
invoc.loweringOpts.setInitGlobalZero(false);

// -finit-local-zero
if (args.hasArg(clang::driver::options::OPT_finit_local_zero)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Braces are not necessary here.

invoc.loweringOpts.setInitLocalZeroDef(1);
}

// Preserve all the remark options requested, i.e. -Rpass, -Rpass-missed or
// -Rpass-analysis. This will be used later when processing and outputting the
// remarks generated by LLVM in ExecuteCompilerInvocation.cpp.
Expand Down
204 changes: 204 additions & 0 deletions flang/lib/Lower/Bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,159 @@ class TypeInfoConverter {
llvm::SmallSetVector<Fortran::semantics::SymbolRef, 32> seen;
};

// Helper class to encapsulate utilities related to emission of implicit
// assignments. `Implicit` here implies the assignment does not
// exist in the Fortran source, but is implicit through definition
// of one or more flagsets (like -finit-* family of flags).
// General purpose usage of these utilities outside the
// scope detailed here is discouraged, and is probably wrong.
class ImplicitAssignmentGenerator {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to create parser:: node to create the assignments?

You could simply work at the FIR level and generate hlfir.assign.

You can get the zero values using builder.createZeroValue for numerical and logical (using the element type from getFortranElementType).

For characters, you can should be able to use fir::factory::CharacterExprHelper::createSingletonFromCode()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. That could be done. Can you help me understand whether such changes will still enable us to do the following:

  1. Make calls like achar(...), which would be required when we upstream support for -finit-character
  2. Make calls like x = ieee_value(x, ieee_quiet_nan), which would be required when we support -finit-real (would fix Flang does not support the -finit-real flag #159794). We also need similar ieee_value calls for inf/nan/snan/-inf.

The reason I thought to handle assignments through parser:: is that I could not understand how we could directly emit such assignments as I exemplified here through HLFIR. Although I understand such calls are not needed for this PR, I intend to share functionality for all -finit-* flags. If there is a way where we can handle these cases in HLFIR, I would be happy to rework this PR.

Copy link
Contributor

@jeanPerier jeanPerier Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createSingletonFromCode is the implementation of achar in lowering, you can use that directly.

For ieee_value(x, ieee_quiet_nan), you can use fir::IntrinsicLibrary::genQNan defined here .

I expect you may want to support snan/inf/+inf, and while there may not be existing FIR level helper to get them, I pretty sure you can easily extract the implementation to get those from ieee_value implementation in IntrinsicCall.cpp (just like genQNan did).

how we could directly emit such assignments as I exemplified here through HLFIR

Why is hlfir.assign not covering your use case? It implements the Fortran intrinsic assignment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay thanks, I have a clearer idea now. Let me explore this and modify this PR.

private:
Fortran::lower::LoweringBridge &bridge;
bool isInitLocalZeroFlagDefined;

/*
* Generates a parser::Variable for `sym` and fills
* in the correct source location for the designator.
*/
Fortran::parser::Variable generateLHS(const Fortran::semantics::Symbol &sym) {
Fortran::parser::Designator designator = Fortran::parser::Designator{
Fortran::parser::DataRef{Fortran::parser::Name{
Fortran::parser::FindSourceLocation(sym.name()),
const_cast<Fortran::semantics::Symbol *>(&sym)}}};
designator.source = Fortran::parser::FindSourceLocation(sym.name());
Fortran::parser::Variable variable = Fortran::parser::Variable{
Fortran::common::Indirection<Fortran::parser::Designator>{
std::move(designator)}};
return variable;
}

/*
* Integer, Real, and Complex types can be initialized through
* IntLiteralConstant. Further implicit casts will ensure
* type compatiblity. Default initialization to `0`.
*/
Fortran::parser::Expr generateIntLiteralConstant(uint32_t init = 0) {
std::string val = std::to_string(init);
return Fortran::parser::Expr{
Fortran::parser::LiteralConstant{Fortran::parser::IntLiteralConstant{
Fortran::parser::CharBlock{val.c_str(), val.size()},
std::optional<Fortran::parser::KindParam>{}}}};
}

/*
* Logical types can be initialized with a LogicalLiteralConstant
* set to <true/false>. Defaults to `false`.
*/
Fortran::parser::Expr generateLogicalLiteralConstant(bool init = false) {
return (init == false)
? Fortran::parser::Expr{Fortran::parser::LiteralConstant{
Fortran::parser::LogicalLiteralConstant{
false, std::optional<Fortran::parser::KindParam>{}}}}
: Fortran::parser::Expr{Fortran::parser::LiteralConstant{
Fortran::parser::LogicalLiteralConstant{
true, std::optional<Fortran::parser::KindParam>{}}}};
}

/*
* Character types can be initialized with a FunctionReference
* to `achar(init)`. Defaults to `achar(0)`.
*/
Fortran::parser::Expr
generateACharReference(Fortran::parser::CharBlock &currentPosition,
const Fortran::semantics::Symbol &sym,
std::string init = "0") {
// Construct a parser::Name for `achar`
std::string funcNameStr = "achar";
Fortran::parser::CharBlock funcCharBlock =
Fortran::parser::CharBlock{funcNameStr.c_str(), funcNameStr.size()};
Fortran::parser::Name funcName = Fortran::parser::Name{
Fortran::parser::CharBlock{funcNameStr.c_str(), funcNameStr.size()}};
Fortran::semantics::Scope &scope =
bridge.getSemanticsContext().FindScope(currentPosition);
Fortran::semantics::Symbol *funcSym = scope.FindSymbol(funcCharBlock);
if (funcSym) {
funcName.symbol = std::move(funcSym);
} else {
Fortran::semantics::Symbol &symbol =
bridge.getSemanticsContext()
.FindScope(currentPosition)
.MakeSymbol(
funcCharBlock,
Fortran::semantics::Attrs{Fortran::semantics::Attr::ELEMENTAL,
Fortran::semantics::Attr::INTRINSIC,
Fortran::semantics::Attr::PURE},
Fortran::semantics::ProcEntityDetails{});
funcName.symbol = std::move(&symbol);
}

// Construct a RHS expression including a FunctionReference
// to `achar(init)`
Fortran::parser::Expr intExpr = Fortran::parser::Expr{
Fortran::parser::LiteralConstant{Fortran::parser::IntLiteralConstant{
Fortran::parser::CharBlock{init.c_str(), init.size()},
std::optional<Fortran::parser::KindParam>{}}}};
Fortran::common::Indirection<Fortran::parser::Expr> indirExpr{
std::move(intExpr)};
Fortran::parser::ActualArg actualArg{std::move(indirExpr)};

Fortran::parser::ActualArgSpec argSpec = Fortran::parser::ActualArgSpec{
std::make_tuple(std::nullopt, std::move(actualArg))};
std::list<Fortran::parser::ActualArgSpec> argSpecList;
argSpecList.push_back(std::move(argSpec));
Fortran::parser::ProcedureDesignator procDesignator =
Fortran::parser::ProcedureDesignator{std::move(funcName)};
Fortran::parser::Call call = Fortran::parser::Call{
std::make_tuple(std::move(procDesignator), std::move(argSpecList))};
Fortran::parser::FunctionReference funcRef =
Fortran::parser::FunctionReference{std::move(call)};
funcRef.source = Fortran::parser::FindSourceLocation(sym.name());
return Fortran::parser::Expr{std::move(funcRef)};
}

/*
* Utility to wrap the LHS variable `var` and RHS expression `expr` into
* an assignment.
*/
inline std::unique_ptr<Fortran::parser::AssignmentStmt>
makeAssignment(Fortran::parser::Variable &var, Fortran::parser::Expr expr) {
Fortran::parser::AssignmentStmt stmt = Fortran::parser::AssignmentStmt{
std::make_tuple(std::move(var), std::move(expr))};
return std::make_unique<Fortran::parser::AssignmentStmt>(std::move(stmt));
}

public:
ImplicitAssignmentGenerator(Fortran::lower::LoweringBridge &bridge,
bool isInitLocalZeroFlagDefined)
: bridge(bridge), isInitLocalZeroFlagDefined(isInitLocalZeroFlagDefined) {
}

std::unique_ptr<Fortran::parser::AssignmentStmt>
emitAssignment(Fortran::parser::CharBlock &currentPosition,
const Fortran::semantics::Symbol &sym) {
if (isInitLocalZeroFlagDefined) {
Fortran::parser::Variable var = generateLHS(sym);
const Fortran::semantics::DeclTypeSpec *declTy = sym.GetType();

if (declTy->IsNumeric(Fortran::semantics::TypeCategory::Integer) ||
declTy->IsNumeric(Fortran::semantics::TypeCategory::Real) ||
declTy->IsNumeric(Fortran::semantics::TypeCategory::Complex))
return makeAssignment(var, generateIntLiteralConstant());

else if (declTy->category() ==
Fortran::semantics::DeclTypeSpec::Category::Logical)
return makeAssignment(var, generateLogicalLiteralConstant());

else if (declTy->category() ==
Fortran::semantics::DeclTypeSpec::Category::Character)
return makeAssignment(var,
generateACharReference(currentPosition, sym));
}

return nullptr;
}
};

using IncrementLoopNestInfo = llvm::SmallVector<IncrementLoopInfo, 8>;
} // namespace

Expand Down Expand Up @@ -5877,6 +6030,57 @@ class FirConverter : public Fortran::lower::AbstractConverter {
void instantiateVar(const Fortran::lower::pft::Variable &var,
Fortran::lower::AggregateStoreMap &storeMap) {
Fortran::lower::instantiateVariable(*this, var, localSymbols, storeMap);

/// Implicit assignment is defined by the `-finit-*` family of flags.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move the logic to where other cases of initializations is handled in instantiateLocal.
That way, it is a lot clearer that this code is not overriding any initializations.

/// These options do not initialize:
/// 1) Any variable already initialized
/// 2) objects with the POINTER attribute
/// 3) allocatable arrays
/// 4) variables that appear in an EQUIVALENCE statement

auto isEligibleForImplicitAssignment = [&var]() -> bool {
if (!var.hasSymbol())
return false;

const Fortran::semantics::Symbol &sym = var.getSymbol();
if (const auto *details =
sym.detailsIf<Fortran::semantics::ObjectEntityDetails>()) {
if (details->init())
return false;
}

if (sym.attrs().test(Fortran::semantics::Attr::POINTER))
return false;

if (sym.Rank() > 0 &&
sym.attrs().test(Fortran::semantics::Attr::ALLOCATABLE))
return false;

if (Fortran::lower::pft::getDependentVariableList(sym).size() > 1)
return false;

return true;
};

if (isEligibleForImplicitAssignment()) {
// Internal state of this class holds only the -finit-* flagsets. Hence
// can be reused for different symbols. Also minimizes the number of
// calls to `getLoweringOptions()`.
static ImplicitAssignmentGenerator implicitAssignmentGenerator{
bridge,
/*isInitLocalZeroFlagDefined=*/getLoweringOptions()
.getInitLocalZeroDef() == 1};
std::unique_ptr<Fortran::parser::AssignmentStmt> stmt =
implicitAssignmentGenerator.emitAssignment(currentPosition,
var.getSymbol());
if (stmt.get()) {
Fortran::evaluate::ExpressionAnalyzer ea{bridge.getSemanticsContext()};
const Fortran::evaluate::Assignment *assign = ea.Analyze(*stmt.get());
if (assign)
genAssignment(*assign);
}
}

if (var.hasSymbol())
genOpenMPSymbolProperties(*this, var);
}
Expand Down
8 changes: 8 additions & 0 deletions flang/test/Driver/finit-local-zero.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
! Check that the driver passes through -finit-global-zero:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
! Check that the driver passes through -finit-global-zero:
! Check that the driver passes through -finit-local-zero:

! RUN: %flang -### -S -finit-local-zero %s -o - 2>&1 | FileCheck %s

! Check that the compiler accepts -finit-local-zero:
! RUN: %flang_fc1 -emit-hlfir -finit-local-zero %s -o -
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider explicitly redirecting to /dev/null if you will not be checking the output. Since this uses -o -, my first thought on looking at it was that a pipe to FileCheck was missing.

Suggested change
! RUN: %flang_fc1 -emit-hlfir -finit-local-zero %s -o -
! RUN: %flang_fc1 -emit-hlfir -finit-local-zero %s -o /dev/null



Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could probably do without this empty line.

! CHECK: "-fc1"{{.*}}"-finit-local-zero"
52 changes: 52 additions & 0 deletions flang/test/Lower/init-local-zero.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
! RUN: %flang_fc1 -emit-fir -finit-local-zero -o - %s | FileCheck %s

!CHECK: %[[const:.*]] = arith.constant 0 : i32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please be consistent with the casing of pattern names. Screaming snake case is used almost exclusively in LLVM, and also in the rest of this test.

Suggested change
!CHECK: %[[const:.*]] = arith.constant 0 : i32
!CHECK: %[[ZERO:.*]] = arith.constant 0 : i32

!CHECK: %[[X:.*]] = fir.alloca i32 {bindc_name = "x", uniq_name = "_QFuninitialized_integerEx"}
!CHECK: %[[X_DECL:.*]] = fir.declare %[[X]] {uniq_name = "_QFuninitialized_integerEx"} : (!fir.ref<i32>) -> !fir.ref<i32>
!CHECK: fir.store %[[const]] to %[[X_DECL]] : !fir.ref<i32>
subroutine uninitialized_integer
integer :: x
end subroutine

!CHECK: %[[const:.*]] = arith.constant 0.000000e+00 : f32
!CHECK: %[[X:.*]] = fir.alloca f32 {bindc_name = "x", uniq_name = "_QFuninitialized_realEx"}
!CHECK: %[[X_DECL:.*]] = fir.declare %[[X]] {uniq_name = "_QFuninitialized_realEx"} : (!fir.ref<f32>) -> !fir.ref<f32>
!CHECK: fir.store %[[const]] to %[[X_DECL]] : !fir.ref<f32>
subroutine uninitialized_real
real :: x
end subroutine

!CHECK: %false = arith.constant false
!CHECK: %[[X:.*]] = fir.alloca !fir.logical<4> {bindc_name = "x", uniq_name = "_QFuninitialized_logicalEx"}
!CHECK: %[[X_DECL:.*]] = fir.declare %[[X]] {uniq_name = "_QFuninitialized_logicalEx"} : (!fir.ref<!fir.logical<4>>) -> !fir.ref<!fir.logical<4>>
!CHECK: %[[CVT:.*]] = fir.convert %false : (i1) -> !fir.logical<4>
!CHECK: fir.store %[[CVT]] to %[[X_DECL]] : !fir.ref<!fir.logical<4>>
subroutine uninitialized_logical
logical :: x
end subroutine

!CHECK: %[[const:.*]] = arith.constant 0.000000e+00 : f32
!CHECK: %[[X:.*]] = fir.alloca complex<f32> {bindc_name = "x", uniq_name = "_QFuninitialized_complexEx"}
!CHECK: %[[X_DECL:.*]] = fir.declare %[[X]] {uniq_name = "_QFuninitialized_complexEx"} : (!fir.ref<complex<f32>>) -> !fir.ref<complex<f32>>
!CHECK: %[[undef:.*]] = fir.undefined complex<f32>
!CHECK: %[[REAL:.*]] = fir.insert_value %[[undef]], %[[const]], [0 : index] : (complex<f32>, f32) -> complex<f32>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The name of this pattern is odd. The result is a complex, but is named real. Maybe C0 (or something else suggestive of a complex number) would be better.

!CHECK: %[[COMPLEX:.*]] = fir.insert_value %[[REAL]], %[[const]], [1 : index] : (complex<f32>, f32) -> complex<f32>
!CHECK: fir.store %[[COMPLEX]] to %[[X_DECL]] : !fir.ref<complex<f32>>
subroutine uninitialized_complex
complex :: x
end subroutine

!CHECK: %[[X:.*]] = fir.alloca !fir.char<1> {bindc_name = "x", uniq_name = "_QFuninitialized_characterEx"}
!CHECK: %[[X_DECL:.*]] = fir.declare %[[X]] typeparams %c1 {uniq_name = "_QFuninitialized_characterEx"} : (!fir.ref<!fir.char<1>>, index) -> !fir.ref<!fir.char<1>>
!CHECK: %[[ADDR:.*]] = fir.address_of(@{{.*}}) : !fir.ref<!fir.char<1>>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to make it clear that one is taking the address of the global that is being matched starting on line 49. That would make it clear that the variable is being initialized to an empty string.

!CHECK: %[[FUNC_DECL:.*]] = fir.declare %[[ADDR]] {{.*}}
!CHECK: %[[LOAD:.*]] = fir.load %[[FUNC_DECL]]
!CHECK: fir.store %[[LOAD]] to %[[X_DECL]]
subroutine uninitialized_character
character :: x
end subroutine

!CHECK: fir.global linkonce @{{.*}} constant : !fir.char<1> {
!CHECK: %[[VAL:.*]] = fir.string_lit "\00"(1) : !fir.char<1>
!CHECK: fir.has_value %[[VAL]] : !fir.char<1>
!CHECK: }
Loading