Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
418f6aa
#3124 Improve codeblock get_symbol_names and add the names as virtual…
sergisiso Nov 25, 2025
9130726
#3124 Update code after creating CodeBlocks virtual children references
sergisiso Nov 25, 2025
7de4e91
Merge remote-tracking branch 'origin/master' into 3124_codeblocks_vir…
sergisiso Nov 28, 2025
f5cd2cf
#3124 make DefUseChain consider new CodeBlock structure
sergisiso Nov 28, 2025
547dad0
Merge remote-tracking branch 'origin/master' into 3124_codeblocks_vir…
sergisiso Dec 2, 2025
c25c853
#3124 Fix or remove tests
sergisiso Dec 2, 2025
7666bd0
#3124 Comment out test with issues
sergisiso Dec 2, 2025
ce99fcf
#3124 Fix flake8
sergisiso Dec 2, 2025
1a6d0c8
Merge remote-tracking branch 'origin/master' into 3124_codeblocks_vir…
sergisiso Dec 2, 2025
b00eb12
#3124 Remove unneeded _get_symbol to find invoke codeblock symbol
sergisiso Dec 3, 2025
402466e
#3124 Remove unused code
sergisiso Dec 3, 2025
9aa682c
#3124 Improve test coverage
sergisiso Dec 3, 2025
e18ad2f
#3124 Improve test coverage
sergisiso Dec 3, 2025
4f25b62
#3124 Add missing docstring
sergisiso Dec 3, 2025
5faa955
#3124 Revert deletion of existing test and instead adapt it without t…
sergisiso Dec 3, 2025
990752a
Merge branch 'master' into 3124_codeblocks_virtual_references
arporter Dec 10, 2025
9e8ebf4
#3124 Improve CodeBlock documentation and errors
sergisiso Dec 12, 2025
5d4036b
#3124 Bring to master
sergisiso Feb 25, 2026
c561196
#3124 Bring to master
sergisiso Mar 3, 2026
f7bfb92
#3124 Temporary bypass a test
sergisiso Mar 3, 2026
3de3393
#3124 Improve tests
sergisiso Mar 4, 2026
dfb4e38
#3124 Improve tests and TODOs
sergisiso Mar 4, 2026
568bf95
Merge remote-tracking branch 'origin/master' into 3124_codeblocks_vir…
sergisiso Mar 6, 2026
34bcdaa
Bring to master
sergisiso Jun 30, 2026
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
24 changes: 15 additions & 9 deletions src/psyclone/psyir/backend/fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,14 +1000,16 @@ def gen_decls(self,
except KeyError:
internal_interface_symbol = None
if unresolved_symbols and not (
symbol_table.wildcard_imports() or internal_interface_symbol):
symbol_table.wildcard_imports() or
internal_interface_symbol or
(symbol_table.node and symbol_table.node.walk(CodeBlock))):
symbols_txt = ", ".join(
["'" + sym.name + "'" for sym in unresolved_symbols])
raise VisitorError(
f"The following symbols are not explicitly declared or "
f"imported from a module and there are no wildcard "
f"imports which could be bringing them into scope: "
f"{symbols_txt}")
f"imports, generic interfaces or CodeBlocks which could be "
f"bringing them into scope: {symbols_txt}")

# Check that the names of all symbols are less than the limit
# imposed by the Fortran standard.
Expand Down Expand Up @@ -1089,19 +1091,23 @@ def filecontainer_node(self, node):
:rtype: str

:raises VisitorError: if the attached symbol table contains
any non-routine symbols.
any symbols that can not be declard in a FileContainer.
:raises VisitorError: if more than one child is a Routine Node
with is_program set to True.

'''
for symbol in node.symbol_table.symbols:
# TODO #2201 - ContainerSymbols should be accepted but
# currently are stored in its containing scope.
if not isinstance(symbol, RoutineSymbol):
# Only RoutineSymbols and ContainerSymbol can be declared here
# pylint: disable=unidiomatic-typecheck
if type(symbol) is Symbol and symbol.is_unresolved:
# However we also accept symbols that we don't know where
# they are declared, so we propagated upwards.
continue

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.

Unfortunately this line isn't covered by the fortran_*test.py tests.

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 I'm here, I'm also not sure what "we propagated upwards" means as we're already handling a FileContainer at this point. Do you mean they will have been propagated upwards and have ended up in this table?

if not isinstance(symbol, (RoutineSymbol, ContainerSymbol)):
raise VisitorError(
f"In the Fortran backend, a file container should not "
f"have any symbols associated with it other than "
f"RoutineSymbols, but found {str(symbol)}.")
f"have any data symbols associated with it, "
f"but found {str(symbol)}.")

program_nodes = len([child for child in node.children if
isinstance(child, Routine) and child.is_program])
Expand Down
4 changes: 2 additions & 2 deletions src/psyclone/psyir/frontend/fparser2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3575,7 +3575,7 @@ def _do_construct_handler(self, node, parent):
self.process_comment(child, preceding_comments)
continue
if isinstance(child, Fortran2003.Directive) and not found_do_stmt:
directive = self._directive_handler(child, None)
directive = self._directive_handler(child, loop.parent)
# Add the directive before the loop.
loop.parent.addchild(directive)
directive.preceding_comment = (
Expand Down Expand Up @@ -3636,7 +3636,7 @@ def _if_construct_handler(self, node, parent):
if isinstance(child, Fortran2003.Comment):
self.process_comment(child, preceding_comments)
if isinstance(child, Fortran2003.Directive):
direc = self._directive_handler(child, None)
direc = self._directive_handler(child, parent)
parent.addchild(direc)
direc.preceding_comment = (
self._comments_list_to_string(preceding_comments)
Expand Down
71 changes: 62 additions & 9 deletions src/psyclone/psyir/nodes/codeblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@
from psyclone.errors import InternalError
from psyclone.psyir.nodes.statement import Statement
from psyclone.psyir.nodes.datanode import DataNode
from psyclone.psyir.nodes.reference import Reference
from psyclone.psyir.nodes.node import Node
from psyclone.psyir.symbols import (
SymbolTable, SymbolError, UnresolvedInterface)


class CodeBlock(Statement, DataNode):
Comment thread
arporter marked this conversation as resolved.
Expand All @@ -73,7 +76,7 @@ class CodeBlock(Statement, DataNode):

'''
#: Textual description of the node.
_children_valid_format = "<LeafNode>"
_children_valid_format = "[Reference]*"
_text_name = "CodeBlock"
_colour = "red"
#: The annotations that are supported by this node.
Expand Down Expand Up @@ -111,6 +114,35 @@ def __init__(
self._parse_tree_nodes = [parse_tree]
# Store the structure of the code block.
self._structure = structure
# Capture all symbols used inside the Codeblock as children References
self._insert_representative_references()
Comment thread
arporter marked this conversation as resolved.

@staticmethod
def _validate_child(position: int, child: Node) -> bool:
'''
:param position: the position to be validated.
:param child: a child to be validated.

:return: whether the given child and position are valid for this node.

'''
return isinstance(child, Reference)

def _insert_representative_references(self):
''' Insert Reference children under this codeblock that
represent each of the symbols used inside the CodeBlock.
'''
for symbol_name in self.get_symbol_names():
try:
symtab = self.scope.symbol_table
except SymbolError:
# Needed for detached CodeBlocks, mainly used in testing
symtab = SymbolTable()
Comment thread
arporter marked this conversation as resolved.
symbol = symtab.find_or_create(
symbol_name, interface=UnresolvedInterface())
ref = Reference(symbol)
if ref not in self.children:
self.addchild(Reference(symbol))

@staticmethod
def create(*args, **kwargs) -> CodeBlock:
Expand Down Expand Up @@ -184,20 +216,20 @@ def reference_accesses(self) -> VariablesAccessMap:
TODO #2863 - it would be better to use AccessType.UNKNOWN here but
currently VariablesAccessMap does not consider that type of access.

This method makes use of
:py:meth:`~psyclone.psyir.nodes.CodeBlock.get_symbol_names` and is
therefore subject to the same limitations as that method.

:returns: a map of all the symbol accessed inside this node, the
keys are Signatures (unique identifiers to a symbol and its
structure accessors) and the values are AccessSequence
(a sequence of AccessTypes).

'''
var_accesses = VariablesAccessMap()
for name in self.get_symbol_names():
var_accesses.add_access(Signature(name), AccessType.READWRITE,
self)
# All symbols accessed within the CodeBlock are captured as Reference
# nodes and stored as children of the CodeBlock node
for child in self.children:
var_accesses.add_access(
Signature(child.name),
AccessType.READWRITE,
child)
return var_accesses

def __str__(self) -> str:
Expand Down Expand Up @@ -294,12 +326,26 @@ def get_symbol_names(self) -> list[str]:
node is node.parent.children[0]):
result.append(node.string)
elif not isinstance(node.parent,
# We don't want labels associated with loop or
# branch control.
(Fortran2003.Cycle_Stmt,
Fortran2003.End_Do_Stmt,
Fortran2003.Exit_Stmt,
Fortran2003.Else_Stmt,
Fortran2003.End_If_Stmt)):
# We don't want labels associated with loop or branch control.

# Check if this name is a structure accessor instead of a
# symbol
if isinstance(node.parent, Fortran2003.Part_Ref):
# Also account for array fields name%array(i)
check = node.parent
else:
check = node
if isinstance(check.parent, Fortran2003.Data_Ref):
# The first child is the base reference, the others are
# accessor names, which are not symbols
if check.parent.children[0] is not check:
continue
result.append(node.string)
# Precision on literals requires special attention since they are just
# stored in the tree as str (fparser/#456).
Comment thread
arporter marked this conversation as resolved.
Expand Down Expand Up @@ -407,3 +453,10 @@ def get_fortran_lines(self) -> list[str]:
for node in self._parse_tree_nodes:
output.extend(str(node.text, encoding="utf8").split("\n"))
return output

def get_symbol_names(self) -> list[str]:
'''
:returns: the name of all symbols accessed in the CodeBlock.
'''
# TODO #3038 Treesitter support is incomplete
return []
4 changes: 4 additions & 0 deletions src/psyclone/psyir/nodes/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def is_write(self):
# pylint: disable=import-outside-toplevel
from psyclone.psyir.nodes.assignment import Assignment
from psyclone.psyir.nodes.call import Call
from psyclone.psyir.nodes.codeblock import CodeBlock
from psyclone.psyir.nodes.intrinsic_call import IntrinsicCall
parent = self.parent
# pure or inquiry IntrinsicCall nodes do not write to their arguments.
Expand All @@ -144,6 +145,9 @@ def is_write(self):
# The reference that is the LHS of an assignment is a write.
if isinstance(parent, Assignment) and parent.lhs is self:
return True
# Assume the worst for CodeBlocks
if isinstance(parent, CodeBlock):
return True
return False

@property
Expand Down
38 changes: 16 additions & 22 deletions src/psyclone/psyir/tools/definition_use_chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,17 +589,14 @@ def _compute_forward_uses(self, basic_block_list: list[Node]):
if defs_out[sig] is not None:
self._defsout[sig].append(defs_out[sig])
return
for i, ref in enumerate(self._references[:]):
if (
ref.symbol.name
in reference.get_symbol_names()
):
# Assume the worst for a CodeBlock and we count
# them as killed and defsout and uses.
sig = self._reference_signatures[i]
if defs_out[sig] is not None:
self._killed[sig].append(defs_out[sig])
defs_out[sig] = reference
elif isinstance(reference.parent, CodeBlock):
for i, _ in enumerate(self._references[:]):
# Assume the worst for a CodeBlock and we count
# them as killed and defsout and uses.
sig = self._reference_signatures[i]
if defs_out[sig] is not None:
self._killed[sig].append(defs_out[sig])
defs_out[sig] = reference
elif isinstance(reference, Call):
# If its a local variable we can ignore it as we'll catch
# the Reference later if its passed into the Call.
Expand Down Expand Up @@ -888,17 +885,14 @@ def _compute_backward_uses(self, basic_block_list: list[Node]):
"DefinitionUseChains can't handle code containing"
" GOTO statements."
)
for i, ref in enumerate(self._references[:]):
if (
ref.symbol.name
in reference.get_symbol_names()
):
# Assume the worst for a CodeBlock and we count
# them as killed and defsout and uses.
sig = self._reference_signatures[i]
if defs_out[sig] is not None:
self._killed[sig].append(defs_out[sig])
defs_out[sig] = reference
elif isinstance(reference.parent, CodeBlock):
for i, _ in enumerate(self._references[:]):
# Assume the worst for a CodeBlock and we count
# them as killed and defsout and uses.
sig = self._reference_signatures[i]
if defs_out[sig] is not None:
self._killed[sig].append(defs_out[sig])
defs_out[sig] = reference
elif isinstance(reference, Call):
# If its a local variable we can ignore it as we'll catch
# the Reference later if its passed into the Call.
Expand Down
2 changes: 1 addition & 1 deletion src/psyclone/psyir/transformations/inline_trans.py
Original file line number Diff line number Diff line change
Expand Up @@ -1157,7 +1157,7 @@ def validate(
continue
exprn = prev.ancestor(Statement, include_self=True)
stmt = exprn.debug_string().strip()
if isinstance(prev, (CodeBlock, Call, Kern, Loop)):
if isinstance(prev, (Kern, Loop)):
Comment thread
arporter marked this conversation as resolved.
raise TransformationError(
f"Cannot inline routine '{routine.name}' "
f"because one or more of its declarations "
Expand Down
19 changes: 3 additions & 16 deletions src/psyclone/psyir/transformations/scalarisation_trans.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
'''This module provides the sclarization transformation class.'''

from psyclone.core import VariablesAccessMap, Signature, SymbolicMaths
from psyclone.psyGen import Kern
from psyclone.psyir.nodes import Call, CodeBlock, Literal, \
IfBlock, Loop, Node, Range, Reference, Routine, StructureReference
from psyclone.psyir.nodes import (
Literal, IfBlock, Loop, Node, Range, Reference, Routine,
StructureReference)
from psyclone.psyir.nodes.array_mixin import ArrayMixin
from psyclone.psyir.symbols import DataSymbol, RoutineSymbol, ScalarType
from psyclone.psyir.transformations.loop_trans import LoopTrans
Expand Down Expand Up @@ -110,12 +110,6 @@ def _is_local_array(signature: Signature,
'''
if not var_accesses[signature].has_indices():
return False
# If any of the accesses are to a CodeBlock then we stop. This can
# happen if there is a string access inside a string concatenation,
# e.g. NEMO4.
for access in var_accesses[signature]:
if isinstance(access.node, CodeBlock):
return False
base_symbol = var_accesses[signature][0].node.symbol
if not base_symbol.is_automatic:
return False
Expand Down Expand Up @@ -307,13 +301,6 @@ def _value_unused_after_loop(sig: Signature,
if has_complex_index:
return False

# If next access is a Call or CodeBlock or Kern then
# we have to assume the value is used. These nodes don't
# have the is_read property that Reference has, so we need
# to be explicit.
if isinstance(next_access, (CodeBlock, Call, Kern)):
Comment thread
arporter marked this conversation as resolved.
return False

# If the access is a read, then return False
if next_access.is_read:
return False
Expand Down
13 changes: 7 additions & 6 deletions src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,9 @@ def test_gen_decls(fortran_writer):
with pytest.raises(VisitorError) as excinfo:
_ = fortran_writer.gen_decls(symbol_table)
assert ("The following symbols are not explicitly declared or imported "
"from a module and there are no wildcard "
"imports which could be bringing them into scope: "
"'unknown'" in str(excinfo.value))
"from a module and there are no wildcard imports, generic "
"interfaces or CodeBlocks which could be bringing them into scope:"
" 'unknown'" in str(excinfo.value))


def test_gen_decls_char(fortran_writer):
Expand Down Expand Up @@ -347,9 +347,10 @@ def test_gen_decls_nested_scope(fortran_writer):
# be brought into scope
with pytest.raises(VisitorError) as err:
fortran_writer.gen_decls(inner_table)
assert ("symbols are not explicitly declared or imported from a module "
"and there are no wildcard imports which "
"could be bringing them into scope: 'unknown1'" in str(err.value))
assert ("The following symbols are not explicitly declared or imported "
"from a module and there are no wildcard imports, generic "
"interfaces or CodeBlocks which could be bringing them into scope:"
" 'unknown1'" in str(err.value))
# Add a ContainerSymbol with a wildcard import in the outermost scope
csym = ContainerSymbol("other_mod")
csym.wildcard_import = True
Expand Down
8 changes: 4 additions & 4 deletions src/psyclone/tests/psyir/backend/fortran_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -958,18 +958,18 @@ def test_fw_filecontainer_2(fortran_writer):
def test_fw_filecontainer_error1(fortran_writer):
'''Check that an instance of the FortranWriter class raises the
expected exception if the symbol table associated with a
FileContainer node contains any symbols.
FileContainer node contains any data symbols.

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 a bit concerned we could now let Symbols through here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reverted


'''
symbol_table = SymbolTable()
symbol_table.add(Symbol("x"))
symbol_table.add(DataSymbol("x", ScalarType.integer_type()))
file_container = FileContainer.create("None", symbol_table, [])
with pytest.raises(VisitorError) as info:
_ = fortran_writer(file_container)
assert (
"In the Fortran backend, a file container should not have any "
"symbols associated with it other than RoutineSymbols, but found "
"x: Symbol<Automatic>." in str(info.value))
"data symbols associated with it, but found x: DataSymbol"
in str(info.value))

# Check that a routine symbol is fine.
symbol_table = SymbolTable()
Expand Down
Loading
Loading