Skip to content

Conversation

@akuhlens
Copy link
Contributor

@akuhlens akuhlens commented Nov 20, 2025

This PR does three things.

  1. Adds a test case that before this PR caused the compiler to crash.
  2. It makes ProcessScopes more resilient to data that hasn't had it's size set, instead it now just returns false to indicate that it encountered an error during processing instead of signalling an internal compiler error.
    • If we decide to omit the reduction in the number of times we run ProcessScopes we will need to keep this change.
    • Otherwise, I think this code should be unreachable, if it is reachable we should have some semantic error that covers the
      reason we were unable to give the variable a size.
    • This might be unacceptable hardening because there might be the possibility of having zero sized arrays that flow to this code. If so, I would appreciate suggestion about how to correct this hardening.
  3. It only runs ProcessScopes at the end of the semantic analysis, therefore
    • If we decide to keep this, we should be able to get rid of the first change.

Mostly presenting both fixes, because I am not sure if there are missing test cases that don't cover why the code wasn't changed to be there second option from the beginning.

@llvmbot llvmbot added flang Flang issues not falling into any other category flang:semantics labels Nov 20, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 20, 2025

@llvm/pr-subscribers-flang-semantics

Author: Andre Kuhlenschmidt (akuhlens)

Changes

This PR does three things.

  1. Adds a test case that before this PR caused the compiler to crash.
  2. It makes ProcessScopes more resilient to data that hasn't had it's size set, instead it now just returns false to indicate that it encountered an error during processing instead of signalling an internal compiler error.
    • If we decide to omit the reduction in the number of times we run ProcessScopes we will need to keep this change.
    • Otherwise, I think this code should be unreachable, if it is reachable we should have some semantic error that covers the
      reason we were unable to give the variable a size.
    • This might be unacceptable hardening because there might be the possibility of having zero sized arrays that flow to this code. If so, I would appreciate suggestion about how to correct this hardening.
  3. It only runs ProcessScopes at the end of the semantic analysis, therefore
    • If we decide to keep this, we should be able to get rid of the first change.

Mostly presenting both fixes, because I am not sure if there are missing test cases that don't cover why the code wasn't changed to be there second option from the beginning.


Full diff: https://github.com/llvm/llvm-project/pull/168909.diff

4 Files Affected:

  • (modified) flang/lib/Semantics/check-data.cpp (+1-1)
  • (modified) flang/lib/Semantics/data-to-inits.cpp (+22-5)
  • (modified) flang/lib/Semantics/data-to-inits.h (+2-2)
  • (added) flang/test/Semantics/equivalence02.f90 (+51)
diff --git a/flang/lib/Semantics/check-data.cpp b/flang/lib/Semantics/check-data.cpp index 3bcf711735158..af89fd7a29e37 100644 --- a/flang/lib/Semantics/check-data.cpp +++ b/flang/lib/Semantics/check-data.cpp @@ -273,7 +273,7 @@ void DataChecker::Leave(const parser::EntityDecl &decl) { } void DataChecker::CompileDataInitializationsIntoInitializers() { - ConvertToInitializers(inits_, exprAnalyzer_); + ConvertToInitializers(inits_, exprAnalyzer_, /*processScopes=*/true); } } // namespace Fortran::semantics diff --git a/flang/lib/Semantics/data-to-inits.cpp b/flang/lib/Semantics/data-to-inits.cpp index bbf3b28fe03e6..af4fffaf9f23b 100644 --- a/flang/lib/Semantics/data-to-inits.cpp +++ b/flang/lib/Semantics/data-to-inits.cpp @@ -863,8 +863,18 @@ static bool ProcessScopes(const Scope &scope, if (std::find_if(associated.begin(), associated.end(), [](SymbolRef ref) { return IsInitialized(*ref); }) != associated.end()) { - result &= - CombineEquivalencedInitialization(associated, exprAnalyzer, inits); + if (std::find_if(associated.begin(), associated.end(), + [](SymbolRef ref) { return !ref->size(); }) != + associated.end()) { + // If a symbol has a non-legacy initialization, it won't have a size, + // so we can't combine its initializations the DataChecker Runs after + // this size is computed and runs this code again so it will have a + // size when encountered later. + result = false; + } else { + result &= CombineEquivalencedInitialization( + associated, exprAnalyzer, inits); + } } } if constexpr (makeDefaultInitializationExplicit) { @@ -944,8 +954,8 @@ void ConstructInitializer(const Symbol &symbol, } } -void ConvertToInitializers( - DataInitializations &inits, evaluate::ExpressionAnalyzer &exprAnalyzer) { +void ConvertToInitializers(DataInitializations &inits, + evaluate::ExpressionAnalyzer &exprAnalyzer, bool processScopes) { // Process DATA-style component /initializers/ now, so that they appear as // default values in time for EQUIVALENCE processing in ProcessScopes. for (auto &[symbolPtr, initialization] : inits) { @@ -953,7 +963,14 @@ void ConvertToInitializers( ConstructInitializer(*symbolPtr, initialization, exprAnalyzer); } } - if (ProcessScopes( + // FIXME: It is kinda weird that we need to repeatedly process the entire + // symbol table each time this is called by LegacyDataInitialization in + // ResolveNames. Could we do this once before the DataChecker and once after + // to combine initializations from Non-Legacy Initialization? Note, it passes + // all tests with just Running this code in + // CompileDataInitializationsIntoInitializers. + if (processScopes && + ProcessScopes( exprAnalyzer.context().globalScope(), exprAnalyzer, inits)) { for (auto &[symbolPtr, initialization] : inits) { if (!symbolPtr->owner().IsDerivedType()) { diff --git a/flang/lib/Semantics/data-to-inits.h b/flang/lib/Semantics/data-to-inits.h index 7486ac8113e90..57fce123ee325 100644 --- a/flang/lib/Semantics/data-to-inits.h +++ b/flang/lib/Semantics/data-to-inits.h @@ -62,8 +62,8 @@ void AccumulateDataInitializations(DataInitializations &, evaluate::ExpressionAnalyzer &, const Symbol &, const std::list<common::Indirection<parser::DataStmtValue>> &); -void ConvertToInitializers( - DataInitializations &, evaluate::ExpressionAnalyzer &); +void ConvertToInitializers(DataInitializations &, + evaluate::ExpressionAnalyzer &, bool ProcessScopes = false); } // namespace Fortran::semantics #endif // FORTRAN_SEMANTICS_DATA_TO_INITS_H_ diff --git a/flang/test/Semantics/equivalence02.f90 b/flang/test/Semantics/equivalence02.f90 new file mode 100644 index 0000000000000..6087f43d8c2c3 --- /dev/null +++ b/flang/test/Semantics/equivalence02.f90 @@ -0,0 +1,51 @@ +! RUN: %python %S/test_errors.py %s %flang_fc1 + +program main +type t1 + sequence + integer, dimension(2):: i/42, 1/  +end type +type t2 + sequence + integer :: j(2) = [41, 1] +end type + +! ERROR: Distinct default component initializations of equivalenced objects affect 'a%i(1_8)' more than once +type (t1) :: A +! ERROR: Distinct default component initializations of equivalenced objects affect 'b%j(1_8)' more than once +type (t2) :: B +! ERROR: Distinct default component initializations of equivalenced objects affect 'x' more than once +integer :: x +data x/42/ +equivalence (A, B, x) +call p(x) +call s() +end + +subroutine s() + type g1 + sequence + integer(kind=8)::d/1_8/ + end type + type g2 + sequence + integer(kind=8)::d = 2_8 + end type + ! ERROR: Distinct default component initializations of equivalenced objects affect 'c%d' more than once + type (g1) :: C + ! ERROR: Distinct default component initializations of equivalenced objects affect 'd%d' more than once + type (g2) :: D + ! ERROR: Distinct default component initializations of equivalenced objects affect 'x' more than once + ! ERROR: Distinct default component initializations of equivalenced objects affect 'y' more than once + integer :: x, y + data x/1/, y/2/ + equivalence (C, x) + equivalence (D, y) + equivalence (x, y) + print *, x, y +end subroutine + +subroutine p(x) + integer :: x + print *, x +end subroutine 
@github-actions
Copy link

🐧 Linux x64 Test Results

  • 4064 tests passed
  • 202 tests skipped
Comment on lines +966 to +971
// FIXME: It is kinda weird that we need to repeatedly process the entire
// symbol table each time this is called by LegacyDataInitialization in
// ResolveNames. Could we do this once before the DataChecker and once after
// to combine initializations from Non-Legacy Initialization? Note, it passes
// all tests with just Running this code in
// CompileDataInitializationsIntoInitializers.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This comment needs to be removed or updated depending on which path we decide to take.

if (std::find_if(associated.begin(), associated.end(),
[](SymbolRef ref) { return !ref->size(); }) !=
associated.end()) {
// If a symbol has a non-legacy initialization, it won't have a size,
Copy link
Contributor

Choose a reason for hiding this comment

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

What about an instance of a derived type that has no components -- if it has an explicit initializer that is an empty structure constructor, won't its size be zero?

Why does a "non-legacy initialization" imply that a symbol won't have a size?

Copy link
Contributor Author

@akuhlens akuhlens Nov 21, 2025

Choose a reason for hiding this comment

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

Legacy Initialization happens during name resolution before this diff (in LegacyDataInitialization), It seems like symbols without legacy initialization don't get there sizes set until after name resolution. You are right about the zero size array, seems like we would need to make size optional to tell if it had been set or not since zero is a valid size.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could see if removing the call to ConvertToInitializers in LegacyDataInitialization would fix the problem too without breaking anything.

Copy link
Contributor

Choose a reason for hiding this comment

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

DATA-style default initializers in PDTs might break; they need to be converted into init() values for each instantiation of the PDT for distinct kind parameter values.

}
}
if (ProcessScopes(
// FIXME: It is kinda weird that we need to repeatedly process the entire
Copy link
Contributor

Choose a reason for hiding this comment

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

agreed

@klausler
Copy link
Contributor

It looks like the change to run ProcessScopes only at the end of semantics fixes this problem and avoids some of the difficulties of the first change. Barring any failures in our test suites, why wouldn't we go with this option?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flang:semantics flang Flang issues not falling into any other category

3 participants