Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 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
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
Original file line number Diff line number Diff line change
Expand Up @@ -90,31 +90,6 @@ def _parse_args(code_block, fp2_node):
# Return the list of detached arguments
return dummy_call.pop_all_children()[1:]

@staticmethod
def _get_symbol(call, fp2_node):
'''Return the name of a Structure Constructor stored as a CodeBlock
containing an fparser2 ast.

:param code_block: the CodeBlock containing a StructureConstructor.
:type code_block: :py:class:`psyclone.psyir.nodes.CodeBlock`
:param fp2_node: the fparser2 Structure Constructor node.
:type fp2_node: \
:py:class:`fparser.two.Fortran2003.Structure_Constructor`

:returns: the symbol capturing the name and type of the \
StructureConstructor.
:rtype: :py:class:`psyclone.psyir.symbols.Symbol`

'''
name = fp2_node.children[0].string
symbol_table = call.scope.symbol_table
try:
type_symbol = symbol_table.lookup(name)
except KeyError:
type_symbol = DataTypeSymbol(name, StructureType())
symbol_table.add(type_symbol)
return type_symbol

@staticmethod
def _specialise_symbol(symbol):
'''If the symbol argument is a Symbol then change it into a
Expand Down Expand Up @@ -243,6 +218,7 @@ def apply(self, node: Call, index: int = None, options=None, **kwargs):

call_name = None
calls = []
symtab = node.scope.symbol_table
for idx, call_arg in enumerate(node.arguments):

# pylint: disable=protected-access
Expand All @@ -261,7 +237,8 @@ def apply(self, node: Call, index: int = None, options=None, **kwargs):
# a StructureConstructor fparser2 node inside
for fp2_node in call_arg.get_ast_nodes:
# This child is a kernel
type_symbol = self._get_symbol(node, fp2_node)
name = fp2_node.children[0].string
type_symbol = symtab.lookup(name)
args = self._parse_args(call_arg, fp2_node)
arg_info.append((type_symbol, args))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ def apply(self, call, index, options=None):
except KeyError:
# No match for a builtin so create a user-defined
# kernel.
type_symbol = RaisePSyIR2AlgTrans._get_symbol(
call, fp2_node)
type_symbol = table.lookup(name)
self._specialise_symbol(type_symbol)
calls.append(LFRicKernelFunctor.create(type_symbol,
args))
Expand Down
14 changes: 8 additions & 6 deletions src/psyclone/psyir/backend/fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -981,14 +981,16 @@ def gen_decls(self, symbol_table, is_module_scope=False):
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}")

# As a convention, we will declare the variables in the following
# order:
Expand Down Expand Up @@ -1069,11 +1071,11 @@ def filecontainer_node(self, node):
for symbol in node.symbol_table.symbols:
# TODO #2201 - ContainerSymbols should be accepted but
Comment thread
arporter marked this conversation as resolved.
Outdated
# currently are stored in its containing scope.
if not isinstance(symbol, RoutineSymbol):
if isinstance(symbol, DataSymbol):
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 @@ -3332,7 +3332,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)
continue
Expand Down Expand Up @@ -3389,7 +3389,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)

# NOTE: The comments are added to the IfBlock node.
Expand Down
55 changes: 51 additions & 4 deletions src/psyclone/psyir/nodes/codeblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
from psyclone.core import AccessType, Signature, VariablesAccessMap
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 Down Expand Up @@ -105,6 +109,33 @@ def __init__(self, fp2_nodes, structure, parent=None, annotations=None):
self.ast_end = None
# Store the structure of the code block.
self._structure = structure
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:
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))

def __eq__(self, other):
'''
Expand Down Expand Up @@ -186,12 +217,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 @@ -249,9 +294,11 @@ def reference_accesses(self) -> VariablesAccessMap:

'''
var_accesses = VariablesAccessMap()
for name in self.get_symbol_names():
var_accesses.add_access(Signature(name), AccessType.READWRITE,
self)
for child in self.children:
Comment thread
arporter marked this conversation as resolved.
var_accesses.add_access(
Signature(child.name),
AccessType.READWRITE,
child)
return var_accesses

def __str__(self):
Expand Down
4 changes: 4 additions & 0 deletions src/psyclone/psyir/nodes/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,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 @@ -133,6 +134,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
44 changes: 24 additions & 20 deletions src/psyclone/psyir/tools/definition_use_chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,16 +483,18 @@ def _compute_forward_uses(self, basic_block_list):
if defs_out is not None:
self._defsout.append(defs_out)
return
if (
self._reference.symbol.name
in reference.get_symbol_names()
):
# Assume the worst for a CodeBlock and we count them
# as killed and defsout and uses.
if defs_out is not None:
self._killed.append(defs_out)
defs_out = reference
continue
for child in reference.children:
if self._reference.symbol is child.symbol:
# It refers to this symbol, assume the worst and
# consider it defout and killed
if defs_out is not None:
self._killed.append(defs_out)
defs_out = child
break
elif isinstance(reference.parent, CodeBlock):
# We have already dealt with References inside Codeblocks
# above, so skip this reference
pass
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 @@ -765,16 +767,18 @@ def _compute_backward_uses(self, basic_block_list):
"DefinitionUseChains can't handle code containing"
" GOTO statements."
)
if (
self._reference.symbol.name
in reference.get_symbol_names()
):
# Assume the worst for a CodeBlock and we count them
# as killed and defsout and uses.
if defs_out is not None:
self._killed.append(defs_out)
defs_out = reference
continue
for child in reference.children:
if self._reference.symbol is child.symbol:
# It refers to this symbol, assume the worst and
# consider it defout and killed
if defs_out is not None:
self._killed.append(defs_out)
defs_out = child
break
elif isinstance(reference.parent, CodeBlock):
# We have already dealt with References inside Codeblocks
# above, so skip this reference
pass
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 @@ -1164,7 +1164,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 @@ -38,9 +38,9 @@
from typing import Optional, Dict, Any, List, Tuple

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, INTEGER_TYPE
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
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ def test_init():
assert invoke_trans._call_name is None


def test_parse_args_get_symbol(fortran_reader):
'''Test that the parse_args and get_symbol methods work as
expected.
def test_parse_args_with_codeblock(fortran_reader):
'''Test that the parse_args works as expected when the invoke
is a CodeBlock.

'''
code = (
Expand All @@ -128,21 +128,6 @@ def test_parse_args_get_symbol(fortran_reader):
assert isinstance(nodes[0], Literal)
assert nodes[0].value == "1.0"

# Check expected output from get_symbol when no symbol exists
with pytest.raises(KeyError):
_ = code_block.scope.symbol_table.lookup("kern")
symbol = RaisePSyIR2AlgTrans._get_symbol(code_block,
code_block._fp2_nodes[0])
assert isinstance(symbol, DataTypeSymbol)
assert symbol.name == "kern"
symbol2 = code_block.scope.symbol_table.lookup("kern")
assert symbol2 is symbol

# Check expected output from get_symbol when symbol already exists
symbol3 = RaisePSyIR2AlgTrans._get_symbol(code_block,
code_block._fp2_nodes[0])
assert symbol3 is symbol


def test_specialise_symbol():
'''Test that the specialise_symbol method work as expected.
Expand Down
15 changes: 8 additions & 7 deletions src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def test_gen_param_decls_case_insensitive(fortran_reader,
"integer(kind=an_int), parameter :: a_second_int = 5_i_def\n"
"integer, parameter :: nfieldnames3d = 4\n"
"integer, dimension(nfieldnames3d), parameter :: "
"interpolationlevels = [2, 0, HUGE(InterpolationLevels) / 3, 0]\n")
"InterpolationLevels = [2, 0, HUGE(InterpolationLevels) / 3, 0]\n")


def test_gen_decls(fortran_writer):
Expand Down Expand Up @@ -250,9 +250,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_array(fortran_writer):
Expand Down Expand Up @@ -300,9 +300,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
Loading
Loading