37 This module contains the InlineTrans transformation.
43 ArrayReference, ArrayOfStructuresReference, BinaryOperation, Call,
44 CodeBlock, Container, IntrinsicCall, Node, Range, Routine, Reference,
45 Return, Literal, Assignment, StructureMember, StructureReference)
48 ArgumentInterface, ArrayType, DataSymbol, UnresolvedType, INTEGER_TYPE,
49 RoutineSymbol, StaticInterface, Symbol, SymbolError, UnknownInterface,
50 UnsupportedType, IntrinsicSymbol)
52 Reference2ArrayRangeTrans)
57 _ONE = Literal(
"1", INTEGER_TYPE)
62 This transformation takes a Call (which may have a return value)
63 and replaces it with the body of the target routine. It is used as
66 >>> from psyclone.psyir.backend.fortran import FortranWriter
67 >>> from psyclone.psyir.frontend.fortran import FortranReader
68 >>> from psyclone.psyir.nodes import Call, Routine
69 >>> from psyclone.psyir.transformations import InlineTrans
73 ... subroutine run_it()
80 ... end subroutine run_it
82 ... real, intent(inout) :: x
84 ... end subroutine sub
85 ... end module test_mod"""
86 >>> psyir = FortranReader().psyir_from_source(code)
87 >>> call = psyir.walk(Call)[0]
88 >>> inline_trans = InlineTrans()
89 >>> inline_trans.apply(call)
90 >>> # Uncomment the following line to see a text view of the schedule
91 >>> # print(psyir.walk(Routine)[0].view())
92 >>> print(FortranWriter()(psyir.walk(Routine)[0]))
95 real, dimension(10) :: a
102 end subroutine run_it
106 Routines/calls with any of the following characteristics are not
107 supported and will result in a TransformationError:
109 * the routine is not in the same file as the call;
110 * the routine contains an early Return statement;
111 * the routine contains a variable with UnknownInterface;
112 * the routine contains a variable with StaticInterface;
113 * the routine contains an UnsupportedType variable with
115 * the routine has a named argument;
116 * the shape of any array arguments as declared inside the routine does
117 not match the shape of the arrays being passed as arguments;
118 * the routine accesses an un-resolved symbol;
119 * the routine accesses a symbol declared in the Container to which it
122 Some of these restrictions will be lifted by #924.
125 def apply(self, node, options=None):
127 Takes the body of the routine that is the target of the supplied
128 call and replaces the call with it.
130 :param node: target PSyIR node.
131 :type node: :py:class:`psyclone.psyir.nodes.Routine`
132 :param options: a dictionary with options for transformations.
133 :type options: Optional[Dict[str, Any]]
138 table = node.scope.symbol_table
142 if not orig_routine.children
or isinstance(orig_routine.children[0],
150 routine = orig_routine.copy()
151 routine_table = routine.symbol_table
157 for child
in routine.children:
158 new_stmts.append(child.copy())
159 refs.extend(new_stmts[-1].walk(Reference))
163 table.merge(routine_table,
173 for child
in node.arguments:
176 ref2arraytrans.apply(child)
177 except (TransformationError, ValueError):
182 formal_args = routine_table.argument_list
188 ancestor_table = node.ancestor(Routine).scope.symbol_table
195 if isinstance(new_stmts[-1], Return):
200 if routine.return_symbol:
202 assignment = node.ancestor(Assignment)
203 parent = assignment.parent
204 idx = assignment.position-1
205 for child
in new_stmts:
207 parent.addchild(child, idx)
208 table = parent.scope.symbol_table
211 routine.return_symbol, table.next_available_name(
212 f
"inlined_{routine.return_symbol.name}"))
213 node.replace_with(
Reference(routine.return_symbol))
218 node.replace_with(new_stmts[0])
219 for child
in new_stmts[1:]:
221 parent.addchild(child, idx)
227 if ancestor_table
is not scope.symbol_table:
228 ancestor_table.merge(scope.symbol_table)
229 replacement = type(scope.symbol_table)()
230 scope.symbol_table.detach()
231 replacement.attach(scope)
233 def _symbols_to_skip(self, table):
235 Constructs a list of those Symbols in the table of the called routine
236 that must be excluded when merging that table with the one at the
240 - those Symbols representing routine arguments;
241 - any RoutineSymbol representing the called routine itself.
243 :param table: the symbol table of the routine to be inlined.
244 :type table: :py:class:`psyclone.psyir.symbols.SymbolTable`
246 :returns: those Symbols that must be skipped when merging the
247 supplied table into the one at the call site.
248 :rtype: list[:py:class:`psyclone.psyir.symbols.Symbol`]
253 symbols_to_skip = table.argument_list[:]
256 rsym = table.lookup_with_tag(
"own_routine_symbol")
257 if isinstance(rsym, RoutineSymbol):
260 symbols_to_skip.append(rsym)
263 return symbols_to_skip
265 def _replace_formal_arg(self, ref, call_node, formal_args):
267 Recursively combines any References to formal arguments in the supplied
268 PSyIR expression with the corresponding Reference (actual argument)
269 from the call site to make a new Reference for use in the inlined code.
270 If the supplied node is not a Reference to a formal argument then it is
271 just returned (after we have recursed to any children).
273 :param ref: the expression to update.
274 :type ref: :py:class:`psyclone.psyir.nodes.Node`
275 :param call_node: the call site.
276 :type call_node: :py:class:`psyclone.psyir.nodes.Call`
277 :param formal_args: the formal arguments of the called routine.
278 :type formal_args: List[:py:class:`psyclone.psyir.symbols.DataSymbol`]
280 :returns: the replacement reference.
281 :rtype: :py:class:`psyclone.psyir.nodes.Reference`
284 if not isinstance(ref, Reference):
286 for child
in ref.children[:]:
290 if ref.symbol
not in formal_args:
295 actual_arg = call_node.arguments[formal_args.index(ref.symbol)]
307 if type(ref)
is Reference:
308 arg_copy = actual_arg.copy()
314 ref.replace_with(arg_copy)
325 if type(actual_arg)
is Reference:
326 ref.symbol = actual_arg.symbol
337 ref.replace_with(new_ref)
340 def _create_inlined_idx(self, call_node, formal_args,
341 local_idx, decln_start, actual_start):
343 Utility that creates the PSyIR for an inlined array-index access
344 expression. This is not trivial since a formal argument may be
345 declared with bounds that are shifted relative to those of an
348 If local_idx is the index of the access in the routine;
349 local_decln_start is the starting index of the dimension as
350 declared in the routine;
351 actual_start is the starting index of the slice at the callsite
352 (whether from the array declaration or a slice);
354 then the index of the inlined access will be::
356 inlined_idx = local_idx - local_decln_start + 1 + actual_start - 1
357 = local_idx - local_decln_start + actual_start
359 :param call_node: the Call that we are inlining.
360 :type call_node: :py:class:`psyclone.psyir.nodes.Call`
361 :param formal_args: the formal arguments of the routine being called.
362 :type formal_args: List[:py:class:`psyclone.psyir.symbols.DataSymbol`]
363 :param local_idx: a local array-index expression (i.e. appearing \
364 within the routine being inlined).
365 :type local_idx: :py:class:`psyclone.psyir.nodes.Node`
366 :param decln_start: the lower bound of the corresponding array \
367 dimension, as declared inside the routine being inlined.
368 :type decln_start: :py:class:`psyclone.psyir.nodes.Node`
369 :param actual_start: the lower bound of the corresponding array \
370 dimension, as defined at the call site.
371 :type actual_start: :py:class:`psyclone.psyir.nodes.Node`
373 :returns: PSyIR for the corresponding inlined array index.
374 :rtype: :py:class:`psyclone.psyir.nodes.Node`
377 if isinstance(local_idx, Range):
379 local_idx.start, decln_start,
382 local_idx.stop, decln_start,
386 return Range.create(lower.copy(), upper.copy(), step.copy())
389 if decln_start == actual_start:
395 start_sub = BinaryOperation.create(BinaryOperation.Operator.SUB,
396 uidx.copy(), ustart.copy())
397 return BinaryOperation.create(BinaryOperation.Operator.ADD,
398 start_sub, actual_start.copy())
400 def _update_actual_indices(self, actual_arg, local_ref,
401 call_node, formal_args):
403 Create a new list of indices for the supplied actual argument
404 (ArrayMixin) by replacing any Ranges with the appropriate expressions
405 from the local access in the called routine. If there are no Ranges
406 then the returned list of indices just contains copies of the inputs.
408 :param actual_arg: (part of) the actual argument to the routine.
409 :type actual_arg: :py:class:`psyclone.psyir.nodes.ArrayMixin`
410 :param local_ref: the corresponding Reference in the called routine.
411 :param call_node: the call site.
412 :type call_node: :py:class:`psyclone.psyir.nodes.Call`
413 :param formal_args: the formal arguments of the called routine.
414 :type formal_args: List[:py:class:`psyclone.psyir.symbols.DataSymbol`]
416 :returns: new indices for the actual argument.
417 :rtype: List[:py:class:`psyclone.psyir.nodes.Node`]
420 if isinstance(local_ref, ArrayMixin):
421 local_indices = [idx.copy()
for idx
in local_ref.indices]
424 if isinstance(local_ref.symbol.datatype, ArrayType):
425 local_decln_shape = local_ref.symbol.datatype.shape
427 local_decln_shape = []
429 new_indices = [idx.copy()
for idx
in actual_arg.indices]
431 for pos, idx
in enumerate(new_indices[:]):
433 if not isinstance(idx, Range):
437 if actual_arg.is_lower_bound(pos):
440 actual_start = actual_arg.get_lbound_expression(pos)
442 actual_start = idx.start
444 local_decln_start =
None
445 if local_decln_shape:
446 if isinstance(local_decln_shape[local_idx_posn],
447 ArrayType.ArrayBounds):
449 local_shape = local_decln_shape[local_idx_posn]
450 local_decln_start = local_shape.lower
451 elif (local_decln_shape[local_idx_posn] ==
452 ArrayType.Extent.DEFERRED):
456 local_decln_start = actual_start
457 if not local_decln_start:
459 local_decln_start = _ONE
461 if local_ref.is_full_range(local_idx_posn):
469 new = Range.create(local_shape.lower.copy(),
470 local_shape.upper.copy())
472 call_node, formal_args,
473 new, local_decln_start, actual_start)
477 call_node, formal_args,
478 local_indices[local_idx_posn],
479 local_decln_start, actual_start)
484 def _replace_formal_struc_arg(self, actual_arg, ref, call_node,
487 Called by _replace_formal_arg() whenever a formal or actual argument
488 involves an array or structure access that can't be handled with a
489 simple substitution, e.g.
491 .. code-block:: fortran
493 call my_sub(my_struc%grid(:,2,:), 10)
495 subroutine my_sub(grid, ngrids)
501 grid(igrid, jgrid)%data(i,j) = 0.0
503 The assignment in the inlined code should become
505 .. code-block:: fortran
507 my_struc%grid(igrid,2,jgrid)%data(i,j) = 0.0
509 This routine therefore recursively combines any References to formal
510 arguments in the supplied Reference (including any array-index
511 expressions) with the corresponding Reference
512 from the call site to make a new Reference for use in the inlined code.
514 :param actual_arg: an actual argument to the routine being inlined.
515 :type actual_arg: :py:class:`psyclone.psyir.nodes.Reference`
516 :param ref: the corresponding reference to a formal argument.
517 :type ref: :py:class:`psyclone.psyir.nodes.Reference`
518 :param call_node: the call site.
519 :type call_node: :py:class:`psyclone.psyir.nodes.Call`
520 :param formal_args: the formal arguments of the called routine.
521 :type formal_args: List[:py:class:`psyclone.psyir.symbols.DataSymbol`]
523 :returns: the replacement reference.
524 :rtype: :py:class:`psyclone.psyir.nodes.Reference`
538 if isinstance(ref, ArrayMixin):
539 local_indices = [idx.copy()
for idx
in ref.indices]
547 if isinstance(cursor, ArrayMixin):
549 cursor, ref, call_node, formal_args)
550 members.append((cursor.name, new_indices))
552 members.append(cursor.name)
554 if not isinstance(cursor, (StructureMember, StructureReference)):
556 cursor = cursor.member
558 if not actual_arg.walk(Range)
and local_indices:
563 for idx
in local_indices:
566 idx.copy(), call_node, formal_args))
569 members[-1] = (cursor.name, new_indices)
576 while isinstance(cursor, (StructureReference, StructureMember)):
577 cursor = cursor.member
578 if isinstance(cursor, ArrayMixin):
580 for idx
in cursor.indices:
585 idx.copy(), call_node, formal_args))
586 members.append((cursor.name, new_indices))
588 members.append(cursor.name)
594 if isinstance(members[0], tuple):
596 return ArrayOfStructuresReference.create(actual_arg.symbol,
599 return StructureReference.create(actual_arg.symbol, members[1:])
602 return ArrayReference.create(actual_arg.symbol, members[0][1])
606 Checks that the supplied node is a valid target for inlining.
608 :param node: target PSyIR node.
609 :type node: subclass of :py:class:`psyclone.psyir.nodes.Call`
610 :param options: a dictionary with options for transformations.
611 :type options: Optional[Dict[str, Any]]
613 :raises TransformationError: if the supplied node is not a Call or is \
615 :raises TransformationError: if the routine has a return value.
616 :raises TransformationError: if the routine body contains a Return \
617 that is not the first or last statement.
618 :raises TransformationError: if the routine body contains a CodeBlock.
619 :raises TransformationError: if the called routine has a named \
621 :raises TransformationError: if any of the variables declared within \
622 the called routine are of UnknownInterface.
623 :raises TransformationError: if any of the variables declared within \
624 the called routine have a StaticInterface.
625 :raises TransformationError: if any of the subroutine arguments is of \
627 :raises TransformationError: if a symbol of a given name is imported \
628 from different containers at the call site and within the routine.
629 :raises TransformationError: if the routine accesses an un-resolved \
631 :raises TransformationError: if the number of arguments in the call \
632 does not match the number of formal arguments of the routine.
633 :raises TransformationError: if a symbol declared in the parent \
634 container is accessed in the target routine.
635 :raises TransformationError: if the shape of an array formal argument \
636 does not match that of the corresponding actual argument.
639 super().
validate(node, options=options)
642 if not isinstance(node, Call):
644 f
"The target of the InlineTrans transformation "
645 f
"should be a Call but found '{type(node).__name__}'.")
647 if isinstance(node, IntrinsicCall):
649 f
"Cannot inline an IntrinsicCall ('{node.routine.name}')")
650 name = node.routine.name
654 if not routine.children
or isinstance(routine.children[0], Return):
658 return_stmts = routine.walk(Return)
660 if len(return_stmts) > 1
or not isinstance(routine.children[-1],
665 f
"Routine '{name}' contains one or more "
666 f
"Return statements and therefore cannot be inlined.")
668 if routine.walk(CodeBlock):
670 f
"Routine '{name}' contains one or more "
671 f
"CodeBlocks and therefore cannot be inlined.")
675 for arg
in node.argument_names:
678 f
"Routine '{routine.name}' cannot be inlined because it "
679 f
"has a named argument '{arg}' (TODO #924).")
681 table = node.scope.symbol_table
682 routine_table = routine.symbol_table
684 for sym
in routine_table.datasymbols:
688 if isinstance(sym.interface, ArgumentInterface):
689 if isinstance(sym.datatype, UnsupportedType):
691 f
"Routine '{routine.name}' cannot be inlined because "
692 f
"it contains a Symbol '{sym.name}' which is an "
693 f
"Argument of UnsupportedType: "
694 f
"'{sym.datatype.declaration}'")
697 if isinstance(sym.interface, UnknownInterface):
699 f
"Routine '{routine.name}' cannot be inlined because it "
700 f
"contains a Symbol '{sym.name}' with an UnknownInterface:"
701 f
" '{sym.datatype.declaration}'")
704 if (isinstance(sym.interface, StaticInterface)
and
705 not sym.is_constant):
707 f
"Routine '{routine.name}' cannot be inlined because it "
708 f
"has a static (Fortran SAVE) interface for Symbol "
714 table.check_for_clashes(
717 except SymbolError
as err:
719 f
"One or more symbols from routine '{routine.name}' cannot be "
720 f
"added to the table at the call site.")
from err
730 ref_or_lits = routine.walk((Reference, Literal))
733 for sym
in routine_table.datasymbols:
734 if sym.initial_value:
736 sym.initial_value.walk((Reference, Literal)))
737 if isinstance(sym.datatype, ArrayType):
738 for dim
in sym.shape:
739 if isinstance(dim, ArrayType.ArrayBounds):
740 if isinstance(dim.lower, Node):
741 ref_or_lits.extend(dim.lower.walk(Reference,
743 if isinstance(dim.upper, Node):
744 ref_or_lits.extend(dim.upper.walk(Reference,
748 _symbol_cache = set()
749 for lnode
in ref_or_lits:
750 if isinstance(lnode, Literal):
751 if not isinstance(lnode.datatype.precision, DataSymbol):
753 sym = lnode.datatype.precision
757 if sym
in _symbol_cache:
759 _symbol_cache.add(sym)
760 if isinstance(sym, IntrinsicSymbol):
763 if sym.is_unresolved:
765 routine_table.resolve_imports(symbol_target=sym)
771 f
"Routine '{routine.name}' cannot be inlined "
772 f
"because it accesses variable '{sym.name}' and this "
773 f
"cannot be found in any of the containers directly "
774 f
"imported into its symbol table.")
776 if sym.name
not in routine_table:
778 f
"Routine '{routine.name}' cannot be inlined because "
779 f
"it accesses variable '{sym.name}' from its "
780 f
"parent container.")
784 if len(routine_table.argument_list) != len(node.arguments):
786 lambda: f
"Cannot inline '{node.debug_string().strip()}' "
787 f
"because the number of arguments supplied to the call "
788 f
"({len(node.arguments)}) does not match the number of "
789 f
"arguments the routine is declared to have "
790 f
"({len(routine_table.argument_list)})."))
792 for formal_arg, actual_arg
in zip(routine_table.argument_list,
796 if not isinstance(formal_arg.datatype, ArrayType):
801 if not isinstance(actual_arg, (Reference, Literal)):
810 lambda: f
"The call '{node.debug_string()}' cannot be "
811 f
"inlined because actual argument "
812 f
"'{actual_arg.debug_string()}' corresponds to a "
813 f
"formal argument with array type but is not a "
814 f
"Reference or a Literal."))
822 if (isinstance(actual_arg.datatype,
823 (UnresolvedType, UnsupportedType))
or
824 (isinstance(actual_arg.datatype, ArrayType)
and
825 isinstance(actual_arg.datatype.intrinsic,
826 (UnresolvedType, UnsupportedType)))):
828 f
"Routine '{routine.name}' cannot be inlined because "
829 f
"the type of the actual argument "
830 f
"'{actual_arg.symbol.name}' corresponding to an array"
831 f
" formal argument ('{formal_arg.name}') is unknown.")
835 if isinstance(formal_arg.datatype, ArrayType):
836 formal_rank = len(formal_arg.datatype.shape)
837 if isinstance(actual_arg.datatype, ArrayType):
838 actual_rank = len(actual_arg.datatype.shape)
839 if formal_rank != actual_rank:
845 lambda: f
"Cannot inline routine '{routine.name}' "
846 f
"because it reshapes an argument: actual argument "
847 f
"'{actual_arg.debug_string()}' has rank {actual_rank}"
848 f
" but the corresponding formal argument, "
849 f
"'{formal_arg.name}', has rank {formal_rank}"))
851 ranges = actual_arg.walk(Range)
853 ancestor_ref = rge.ancestor(Reference)
854 if ancestor_ref
is not actual_arg:
858 lambda: f
"Cannot inline routine '{routine.name}' "
859 f
"because argument '{actual_arg.debug_string()}' "
860 f
"has an array range in an indirect access (TODO "
867 lambda: f
"Cannot inline routine '{routine.name}' "
868 f
"because one of its arguments is an array slice "
869 f
"with a non-unit stride: "
870 f
"'{actual_arg.debug_string()}' (TODO #1646)"))
873 def _find_routine(call_node):
874 '''Searches for the definition of the routine that is being called by
877 :param call_node: the Call that is to be inlined.
878 :type call_node: :py:class:`psyclone.psyir.nodes.Call`
880 :returns: the PSyIR for the target routine.
881 :rtype: :py:class:`psyclone.psyir.nodes.Routine`
883 :raises InternalError: if the routine symbol is local but the \
884 routine definition is not found.
885 :raises TransformationError: if the routine definition cannot be found.
888 name = call_node.routine.name
889 routine_sym = call_node.routine.symbol
891 if routine_sym.is_modulevar:
892 table = routine_sym.find_symbol_table(call_node)
893 for routine
in table.node.walk(Routine):
894 if routine.name.lower() == name.lower():
897 f
"Failed to find the source code of the local routine "
898 f
"'{routine_sym.name}'.")
900 if routine_sym.is_unresolved:
905 current_table = call_node.scope.symbol_table
907 for container_symbol
in current_table.containersymbols:
908 if container_symbol.wildcard_import:
909 wildcard_names.append(container_symbol.name)
910 routine = InlineTrans._find_routine_in_container(
911 call_node, container_symbol)
914 current_table = current_table.parent_symbol_table()
921 for routine
in call_node.root.children:
922 if (isinstance(routine, Routine)
and
923 routine.name.lower() == name.lower()):
926 f
"Failed to find the source code of the unresolved "
927 f
"routine '{name}' after trying wildcard imports from "
928 f
"{wildcard_names} and all routines that are not in "
931 if routine_sym.is_import:
932 container_symbol = routine_sym.interface.container_symbol
933 routine = InlineTrans._find_routine_in_container(
934 call_node, container_symbol)
938 f
"Failed to find the source for routine '{routine_sym.name}' "
939 f
"imported from '{container_symbol.name}' and therefore "
940 f
"cannot inline it.")
943 f
"Routine Symbol '{routine_sym.name}' is not local, "
944 f
"unresolved or imported.")
947 def _find_routine_in_container(call_node, container_symbol):
948 '''Searches for the definition of a routine that is being called by
949 the supplied Call. If present, this routine must exist within a
950 container specified by the supplied container symbol.
952 :param call_node: the Call that is to be inlined.
953 :type call_node: :py:class:`psyclone.psyir.nodes.Call`
955 :param container_symbol: the symbol of the container to search.
956 :type container_symbol: \
957 :py:class:`psyclone.psyir.symbols.ContainerSymbol`
959 :returns: the PSyIR for the target routine, if found.
960 :rtype: Optional[:py:class:`psyclone.psyir.nodes.Routine`]
968 call_routine_sym = call_node.routine
969 for container
in call_node.root.children:
970 if (isinstance(container, Container)
and
971 container.name.lower() == container_symbol.name.lower()):
972 for routine
in container.children:
973 if (isinstance(routine, Routine)
and
974 routine.name.lower() ==
975 call_routine_sym.name.lower()):
977 routine_sym = container.symbol_table.lookup(
979 if routine_sym.visibility == Symbol.Visibility.PUBLIC:
985 table = container.symbol_table
987 routine_sym = table.lookup(call_routine_sym.name)
988 if routine_sym.is_import:
989 child_container_symbol = \
990 routine_sym.interface.container_symbol
991 return (InlineTrans._find_routine_in_container(
992 call_node, child_container_symbol))
997 for child_container_symbol
in table.containersymbols:
998 if child_container_symbol.wildcard_import:
999 result = InlineTrans._find_routine_in_container(
1000 call_node, child_container_symbol)
1010 __all__ = [
"InlineTrans"]