Skip to content

Updates for inlining with common variables#3389

Open
schreiberx wants to merge 22 commits intomasterfrom
martin_common
Open

Updates for inlining with common variables#3389
schreiberx wants to merge 22 commits intomasterfrom
martin_common

Conversation

@schreiberx
Copy link
Copy Markdown
Collaborator

No description provided.

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.96%. Comparing base (59b71f5) to head (8bc9306).

Additional details and impacted files
@@           Coverage Diff            @@
##           master    #3389    +/-   ##
========================================
  Coverage   99.96%   99.96%            
========================================
  Files         389      389            
  Lines       54598    54710   +112     
========================================
+ Hits        54579    54692   +113     
+ Misses         19       18     -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Member

@arporter arporter left a comment

Choose a reason for hiding this comment

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

Thanks @schreiberx, I learnt (or reminded myself) quite a lot about common blocks :-)
Unfortunately, it is currently impossible to do what you want safely as PSyclone does not capture which common a variable with a 'common block' interface is associated with. Since the names of common blocks are global and don't live in the same space as variables, I think extending PSyclone to at least capture those names and the symbols associated with them should be relatively straightforward. The code in question is:

if isinstance(node, Fortran2003.Common_Stmt):
# Place the declaration statement into a UnsupportedFortranType
# (for now we just want to reproduce it). The name of the
# commonblock is not in the same namespace as the variable
# symbols names (and there may be multiple of them in a
# single statement). So we use an internal symbol name.
psyir_parent.symbol_table.new_symbol(
root_name="_PSYCLONE_INTERNAL_COMMONBLOCK",
symbol_type=DataSymbol,
datatype=UnsupportedFortranType(str(node)))
# Get the names of the symbols accessed with the commonblock,
# they are already defined in the symbol table but they must
# now have a common-block interface.
try:
# Loop over every COMMON block defined in this Common_Stmt
for cb_object in node.children[0]:
for symbol_name in cb_object[1].items:
sym = psyir_parent.symbol_table.lookup(
str(symbol_name))
if sym.initial_value:
# This is C506 of the F2008 standard.
raise NotImplementedError(
f"Symbol '{sym.name}' has an initial value"
f" ({sym.initial_value.debug_string()}) "
f"but appears in a common block. This is "
f"not valid Fortran.")
sym.interface = CommonBlockInterface()

Given that we've learnt to our cost that safety needs to come first, I suggest we need to extend PSyclone's handling of common a little before we can safely handle common blocks when merging tables.

if (isinstance(old_sym.datatype, UnsupportedFortranType) and
isinstance(self_sym.datatype, UnsupportedFortranType)):
if old_sym.datatype.declaration == self_sym.datatype.declaration:
# Identical COMMON-block markers – already present in self.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This comment isn't right - they are just the same unsupported type, not necessarily a common block.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In fact, this is quite confusing. I think what we have here is that the two symbols that have a name collision both have a 'common block' interface and also happen to be of UnsupportedFortranType. I think (but could be wrong) that it would also be possible for them to be of a supported type. Unfortunately, we have no way of knowing whether they are both the (same) variable from the same common block.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm afraid I'm still confused here. The check at L1038 means that only one or none of the clashing symbols has a common-block interface. Therefore, old_sym and self_sym can't both be common-block markers and in fact both might not be? Am I misunderstanding something?

Comment thread src/psyclone/psyir/symbols/symbol_table.py Outdated
Comment thread src/psyclone/psyir/symbols/symbol_table.py Outdated
for s in self.symbols if s.is_commonblock)

# Normalised names of commonblock variables in other_table. Any
# overlap with self_cb_names means the COMMON block is already
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Worth extending this comment to say that the same variable cannot appear in more than one common block so if they have the same name and a 'common block' interface then they must originate from the same common. I'm slightly uneasy as the actual names of symbols within a common block don't have to match: just their order and type:

Image

So, two symbols from different scopes but with the same name and a common block interface are not guaranteed to be referring to the same thing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Consider:

integer :: var1, var2
common /block1/ var1, var2

and (in a different scope):

real :: var1, var2
common /block2/ var1, var2

as written, the code would assume that it is safe to ignore this clash because both var1 and var2 have a 'common block' interface. Unfortunately we currently make no attempt to capture the name of a common block - the whole common /.... statement is just reproduced blindly.

Comment thread src/psyclone/psyir/symbols/symbol_table.py Outdated
Comment thread src/psyclone/psyir/symbols/symbol_table.py Outdated
Comment thread src/psyclone/psyir/symbols/symbol_table.py Outdated
Comment thread src/psyclone/psyir/symbols/symbol_table.py Outdated
@schreiberx
Copy link
Copy Markdown
Collaborator Author

Since this is public, please also let me add that I informed you in advance that this PR is AI-generated and not proofread.
I forgot to mention this, and I think it's important to note it here.

@arporter
Copy link
Copy Markdown
Member

arporter commented Apr 1, 2026

Since this is public, please also let me add that I informed you in advance that this PR is AI-generated and not proofread. I forgot to mention this, and I think it's important to note it here.

Thanks Martin, that's a good point. I would have worded some of my comments differently if I knew you had personally written the code :-)

@schreiberx
Copy link
Copy Markdown
Collaborator Author

This would solve #2554

@schreiberx
Copy link
Copy Markdown
Collaborator Author

@arporter Back to you.

@schreiberx
Copy link
Copy Markdown
Collaborator Author

I updated the tests.
I also saw from your other branch that you wanted to update some URLs, which I also did in this branch.

@arporter
Copy link
Copy Markdown
Member

Thanks Martin, if you could fix the linting so that the test suite runs through then I'll take another look.

@schreiberx
Copy link
Copy Markdown
Collaborator Author

linting fixed.

@arporter
Copy link
Copy Markdown
Member

Hi @schreiberx, please could you fix the test failure and then make sure the coverage is OK too?

@schreiberx
Copy link
Copy Markdown
Collaborator Author

Hi Andy, I fixed this and added some new code to address another issue with Croco.

Copy link
Copy Markdown
Member

@arporter arporter left a comment

Choose a reason for hiding this comment

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

Nice, thanks Martin. It's impressive to see what can be done now.
There's a bit more code moving to do and I'm still a bit worried that we'll let through overlapping common-block declarations that use different local variable names.
Apart from that, it would be great to add compilation checks to all of the new tests you've added. You need to:

  • Add the tmp_path test fixture;
  • output = fortran_writer(psyir)
  • Add a Compile(tmp_path).string_compiles(output)


self_sym = self.lookup(old_sym.name)
if old_sym.is_unresolved and self_sym.is_unresolved:
# Update after fixing issue #3392
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this routine assumes check_for_clashes has been called successfully, this comment doesn't belong here. However, we could do with a comment at 1039 to say that check_for_clashes must have determined that the two variables are from the same common block.

if (isinstance(old_sym.datatype, UnsupportedFortranType) and
isinstance(self_sym.datatype, UnsupportedFortranType)):
if old_sym.datatype.declaration == self_sym.datatype.declaration:
# Identical COMMON-block markers – already present in self.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm afraid I'm still confused here. The check at L1038 means that only one or none of the clashing symbols has a common-block interface. Therefore, old_sym and self_sym can't both be common-block markers and in fact both might not be? Am I misunderstanding something?

# We've dealt with Container symbols in _add_container_symbols.
continue

# Avoid duplicate COMMON-block marker symbols when multiple
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This new logic belongs in _handle_symbol_clash - that's where we determine whether or not to take any action to resolve a clash.

# (PARAMETER) symbols and redirect their references *before* the
# routine body is extracted, so that the extracted statements already
# carry references to the call-site symbols.
extra_skip: List[DataSymbol] = []
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can use "list" rather than "List".

extra_skip: List[DataSymbol] = []
if not parameter_cloning:
extra_skip = self._redirect_duplicate_parameters(
table, routine, routine_table)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please don't pass routine_table - it must always be the one that belongs to routine.

assert result.count("parameter :: base_val") == 1


def test_apply_parameter_cloning_false_unary_op_identical(fortran_reader,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think you need this test - it's really just testing the __eq__ of DataNode.

# negflag depends on flag which differs, so foo's negflag must also
# appear (renamed), and the inlined if must use foo's (renamed) negflag.
assert "negflag_1" in result
assert "if (negflag_1)" in result
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Compilation check pls.

constval = 7
b = constval_1

end subroutine bar""" in result
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pls check compilation too.


b = INT(constval_1)

end subroutine bar""" in result
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Compilation pls.

end subroutine bar
subroutine foo(a)
logical, parameter :: flag = .false.
logical, parameter :: negflag = .not. flag
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Worth doing a similar test where a kind parameter is duplicated but different between the call site and the callee, e.g.:

integer, parameter :: wp = kind(1.0d0)
real(kind=wp), parameter :: pi = 3.14592

and the same in the callee but with:

integer, parameter :: wp = kind(1.0)

@schreiberx
Copy link
Copy Markdown
Collaborator Author

Thanks for your feedback. I'm trying to summarize all changes briefly:

  • The compilation feature was added to most of the inlining test cases. This requires running with pytest --compile.
  • I moved almost all common-related parts in the symbol table into a dedicated function. We need to treat "common" variables partly differently and moving this handling into a separate function was IMHO the best solution to keep the code clearer.
  • I added also further support / test cases to handle merges if the variable names aren't matching. They don't need to match; they just need to match the type.

@schreiberx
Copy link
Copy Markdown
Collaborator Author

@arporter Next round :-)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants