From a7ce67b3074df9ff97e476c3b7fa60015393cfdf Mon Sep 17 00:00:00 2001 From: Santeri Karppinen Date: Fri, 13 Oct 2023 12:09:32 +0300 Subject: [PATCH] added modified docs --- docs/source/notebooks.rst | 1 + ...06_transformations_and_the_scheduler.ipynb | 604 ++++++++++++++++++ example/src/depth1.F90 | 39 ++ example/src/depth2.F90 | 35 + example/src/wrapper.F90 | 20 + 5 files changed, 699 insertions(+) create mode 100644 example/06_transformations_and_the_scheduler.ipynb create mode 100644 example/src/depth1.F90 create mode 100644 example/src/depth2.F90 create mode 100644 example/src/wrapper.F90 diff --git a/docs/source/notebooks.rst b/docs/source/notebooks.rst index aabae55cb..55b4371f6 100644 --- a/docs/source/notebooks.rst +++ b/docs/source/notebooks.rst @@ -14,3 +14,4 @@ to get familiar with the API and usage. example/03_loop_fusion example/04_creating_new_visitors example/05_argument_intent_linter + example/06_transformations_and_the_scheduler diff --git a/example/06_transformations_and_the_scheduler.ipynb b/example/06_transformations_and_the_scheduler.ipynb new file mode 100644 index 000000000..0c62aded9 --- /dev/null +++ b/example/06_transformations_and_the_scheduler.ipynb @@ -0,0 +1,604 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cc566b42", + "metadata": {}, + "source": [ + "# Transformations and the Scheduler" + ] + }, + { + "cell_type": "markdown", + "id": "e938b3d3", + "metadata": {}, + "source": [ + "The purpose of this notebook is to explain how Loki `Transformation`s are defined and used, and explain how transformations can be used to process entire call trees using the `Scheduler`. This notebook assumes that the reader is already familiar with the basics of Loki." + ] + }, + { + "cell_type": "markdown", + "id": "bfc0f93e", + "metadata": {}, + "source": [ + "## Defining a transformation as a Python class" + ] + }, + { + "cell_type": "markdown", + "id": "50300446-ada5-4cf5-a3fb-cd39a4235753", + "metadata": {}, + "source": [ + "Recall that to implement a new transformation as a Python class in Loki, one must define a class that:\n", + "1. Inherits from the abstract class `loki.Transformation`.\n", + "2. Implements _at least one_ of the methods making up the so-called \"transformation interface\" of Loki:\n", + " - `transform_subroutine(routine: loki.Subroutine, **kwargs) -> None`,\n", + " - `transform_module(module: loki.Module, **kwargs) -> None`,\n", + " - `transform_file(sourcefile: loki.Sourcefile, **kwargs) -> None`.\n", + " \n", + "The methods making up the transformation interface describe how the transformation should process subroutines, modules and whole sourcefiles, respectively. Note that the user is free to pass arbitrary data (needed in the transformation) to the transformation in its constructor. " + ] + }, + { + "cell_type": "markdown", + "id": "d40ee91c-a27e-499d-9f53-19c463bb5dd2", + "metadata": {}, + "source": [ + "As a simple example, consider a transformation that removes a dummy argument with a certain name from `Subroutine`s." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0cf7cd00-a648-4b41-a5c3-d9a3b7223382", + "metadata": {}, + "outputs": [], + "source": [ + "from loki import Transformation, FindNodes, VariableDeclaration, Transformer\n", + "class RemoveDummyArgTransformation(Transformation):\n", + " # NOTE: here the constructor of the transformation takes the string `to_remove` as input.\n", + " def __init__(self, to_remove):\n", + " super().__init__()\n", + " self.to_remove = to_remove\n", + "\n", + " def transform_subroutine(self, routine, **kwargs):\n", + " # Remove dummy argument from argument list.\n", + " routine.arguments = (var for var in routine.arguments if var.name != self.to_remove)\n", + "\n", + " # Remove dummy argument from specification.\n", + " transmap = {}\n", + " for vd in FindNodes(VariableDeclaration).visit(routine.spec):\n", + " if self.to_remove in (v.name for v in vd.symbols):\n", + " new_symbols = [var.clone() for var in vd.symbols if var.name != self.to_remove]\n", + " if new_symbols:\n", + " transmap[vd] = vd.clone(symbols = new_symbols)\n", + " else:\n", + " transmap[vd] = None\n", + " routine.spec = Transformer(transmap).visit(routine.spec)\n", + "\n", + " # Behaviour in case of a Sourcefile is to apply the transformation \n", + " # to all subroutines in the file, and print the name of the routine just before transforming it.\n", + " def transform_file(self, file, **kwargs):\n", + " for routine in file.all_subroutines:\n", + " print(f\"Processing routine: {routine.name}\")\n", + " self.transform_subroutine(routine, **kwargs)" + ] + }, + { + "cell_type": "markdown", + "id": "6e1bd10f-94b5-4714-b0a0-bcddddb4dd3d", + "metadata": {}, + "source": [ + "We may apply the transformation to some example code as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "198cbe21-8aa6-46f8-a24d-d9e02061fc96", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing routine: test\n", + "\n", + "SUBROUTINE test (y)\n", + " INTEGER, INTENT(INOUT) :: y\n", + " y = 4 + x\n", + "END SUBROUTINE test\n" + ] + } + ], + "source": [ + "from loki import Sourcefile\n", + "fcode = \"\"\"\n", + "SUBROUTINE test(x, y)\n", + " INTEGER, INTENT(IN) :: x\n", + " INTEGER, INTENT(INOUT) :: y\n", + " y = 4 + x\n", + "END SUBROUTINE test\n", + "\"\"\"\n", + "src = Sourcefile.from_source(fcode)\n", + "\n", + "# Create transformation to remove a dummy argument named `x`.\n", + "transformation = RemoveDummyArgTransformation(to_remove = \"x\")\n", + "\n", + "# Apply the transformation.\n", + "transformation.apply(src)\n", + "\n", + "# Check results after transforming.\n", + "print(src.to_fortran())" + ] + }, + { + "cell_type": "markdown", + "id": "3be3710f-acec-410e-bdbd-8c014f58f509", + "metadata": {}, + "source": [ + "Note that Loki comes with a number of predefined transformations: https://sites.ecmwf.int/docs/loki/main/transform.html " + ] + }, + { + "cell_type": "markdown", + "id": "22385b84-d13c-4c46-ac72-06bd160847b9", + "metadata": {}, + "source": [ + "## Processing source trees using the `Scheduler`" + ] + }, + { + "cell_type": "markdown", + "id": "a2549f9d-9614-4342-98ac-f361a212d8fa", + "metadata": {}, + "source": [ + "The above example showed how to make a transformation at the level of an individual subroutine, module or sourcefile. However, in practice, most source code bases have multiple sourcefiles that can have complex interdependencies. In such cases, the approach of modifying single entities at a time may become inefficient. Next, we will look at how we can use Loki to more effectively work in such situations, and apply transformations or even transformation pipelines (consisting of multiple transformations in sequence) at the level of full \"codebases\". The main tool to facilitate this is the `Scheduler` object." + ] + }, + { + "cell_type": "markdown", + "id": "c7b64924-c871-4eff-8b57-4d33d7b98e41", + "metadata": {}, + "source": [ + "As an example, we will look at a \"codebase\" consisting of three subroutines 'wrapper', 'depth1' and 'depth2' in three separate files. The subroutines form the following call tree:\n", + "- 'wrapper' in 'wrapper.F90' calls 'depth1' in 'depth1.F90'\n", + "- depth1 calls 'depth2' in 'depth2.F90'" + ] + }, + { + "cell_type": "markdown", + "id": "7369e04e-6adb-4a27-b2fc-67cc2a5d9206", + "metadata": {}, + "source": [ + "**Subroutine 'wrapper'**" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "751c7f38-6e3f-4fa2-93f6-3f1679e348b7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "subroutine wrapper\n", + " implicit none\n", + "\n", + " integer :: h_start = 1\n", + " integer, parameter :: h_dim = 10\n", + " integer :: h_end \n", + " integer :: v_start = 1\n", + " integer, parameter :: v_dim = 20\n", + " integer, parameter :: block_size = 30\n", + " integer :: block_index\n", + "\n", + " real :: arr(h_dim, v_dim, block_dim)\n", + "\n", + " h_end = h_dim\n", + " do block_index = 1, block_size \n", + " call depth1(h_start, h_end, h_dim, v_start, v_dim, &\n", + " arr(:, :, block_index))\n", + " end do\n", + " print *, \"Sum of array is \", sum(arr)\n", + "end subroutine wrapper\n", + "\n" + ] + } + ], + "source": [ + "print(open(\"src/wrapper.F90\", \"r\").read())" + ] + }, + { + "cell_type": "markdown", + "id": "04286490-ca06-4aed-8bb7-46a0f93aba7c", + "metadata": {}, + "source": [ + "**Subroutine 'depth1'**" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "0e074638-c7aa-4066-a02c-9a5e32e778ec", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "subroutine depth1(h_start, h_end, h_dim, v_start, v_dim, array)\n", + " implicit none\n", + "\n", + " ! Arguments.\n", + " integer, intent(in) :: h_start\n", + " integer, intent(in) :: h_end\n", + " integer, intent(in) :: h_dim\n", + " integer, intent(in) :: v_start\n", + " integer, intent(in) :: v_dim\n", + " real, intent(inout) :: array(h_dim, v_dim)\n", + "\n", + " ! Non-array local variables.\n", + " integer :: v_index\n", + " integer :: h_index\n", + "\n", + " ! Temporary arrays.\n", + " real :: tmp1(h_dim, v_dim)\n", + "\n", + " do v_index = v_start, v_dim\n", + " do h_index = h_start, h_end\n", + " tmp1(h_index, v_index) = exp(log(real(h_index)) + log(real(v_index)) - 1.0)\n", + " end do\n", + " end do\n", + "\n", + " do v_index = v_start, v_dim\n", + " do h_index = h_start, h_end\n", + " array(h_index, v_index) = exp(tmp1(h_index, v_index) + 0.25)\n", + " end do\n", + " end do\n", + "\n", + " call depth2(h_start, h_end, h_dim, v_start, v_dim, array)\n", + "\n", + " do v_index = v_start, v_dim\n", + " do h_index = h_start, h_end\n", + " array(h_index, v_index) = log(tmp1(h_index, v_index)) + array(h_index, v_index) * 2.0\n", + " end do\n", + " end do\n", + "\n", + "end subroutine depth1\n", + "\n" + ] + } + ], + "source": [ + "print(open(\"src/depth1.F90\", \"r\").read())" + ] + }, + { + "cell_type": "markdown", + "id": "304f1ba0-7816-4d45-87fa-b784684f3700", + "metadata": {}, + "source": [ + "**Subroutine 'depth2'**" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "a6b055e0-04d8-462a-a8f1-7c4b4531151f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "subroutine depth2(h_start, h_end, h_dim, v_start, v_dim, array)\n", + " implicit none\n", + " integer, intent(in) :: h_start\n", + " integer, intent(in) :: h_end\n", + " integer, intent(in) :: h_dim\n", + " integer, intent(in) :: v_start\n", + " integer, intent(in) :: v_dim\n", + " real, intent(inout) :: array(h_dim, v_dim)\n", + "\n", + " ! Non-array local variables.\n", + " integer :: v_index\n", + " integer :: h_index\n", + " real :: val\n", + "\n", + " ! Temporary arrays.\n", + " real :: tmp2(h_dim, v_dim)\n", + "\n", + " val = 1.0\n", + " call contained(val)\n", + " tmp2 = 2.0 + val\n", + " tmp2(h_dim, v_dim) = -1.0\n", + "\n", + " do v_index = v_start, v_dim\n", + " do h_index = h_start, h_end\n", + " array(h_index, v_index) = array(h_index, v_index) + tmp2(h_index, v_index)\n", + " end do\n", + " end do\n", + "contains\n", + "\n", + " subroutine contained(x)\n", + " real, intent(inout) :: x\n", + " x = x * 2.0\n", + " end subroutine contained\n", + "\n", + "end subroutine depth2\n", + "\n" + ] + } + ], + "source": [ + "print(open(\"src/depth2.F90\", \"r\").read())" + ] + }, + { + "cell_type": "markdown", + "id": "fcf16f92-f8e1-4b9e-88bd-da5ba3ad3608", + "metadata": {}, + "source": [ + "### Applying a transformation to a full code base" + ] + }, + { + "cell_type": "markdown", + "id": "5f4d283d-f2de-4da9-837f-b2f7b56c6b8f", + "metadata": {}, + "source": [ + "NOTE, TODO: I think somewhere in this section it would make sense to explicitly list what data the Scheduler is passing to the transformations. Perhaps there are other ways of passing data also, that have not been covered here." + ] + }, + { + "cell_type": "markdown", + "id": "76484aae-872f-493a-9d54-2edf618d6563", + "metadata": {}, + "source": [ + "To make transformations for the full code base, we need to first construct a `Scheduler` object, which in summary is responsible for:\n", + "\n", + "1. Building a call tree of the program to be processed\n", + "2. Managing the application of transformations to the call tree\n", + "\n", + "Constructing a `Scheduler` requires some configuration in the form of a `SchedulerConfig` object. The purpose of `SchedulerConfig` is to store data on how individual subroutines (and modules and sourcefiles?) should be transformed (TODO: Maybe something else as well?). As we will see in a bit, this data is passed by the `Scheduler` to the transformations that are eventually applied to transform the full program." + ] + }, + { + "cell_type": "markdown", + "id": "d68467c8-1a23-4b19-8d25-8fd3919adee5", + "metadata": {}, + "source": [ + "To make this more concrete, let's consider an example where we would like to rename (some of) the subroutines in the call tree formed by the Fortran subroutines above. We write the following transformation (intended to be used with the `Scheduler`):" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "f063d8a1-df8f-4d90-89bf-49ebb1cd46b7", + "metadata": {}, + "outputs": [], + "source": [ + "class RenameWithSchedulerTransformation(Transformation):\n", + " def __init__(self):\n", + " super().__init__()\n", + "\n", + " def transform_subroutine(self, routine, **kwargs):\n", + " # The Scheduler implicitly passes a field named 'item' to the keyword arguments of the transformation.\n", + " item = kwargs.get('item', None)\n", + " newname = None\n", + " if item:\n", + " newname = item.config['newname'] \n", + " if newname:\n", + " print(f\"Found a new name '{newname}' when processing subroutine '{routine.name}', renaming..\")\n", + " routine.name = newname" + ] + }, + { + "cell_type": "markdown", + "id": "f1719b97-7df1-4eb2-9fed-449eaea1fcfa", + "metadata": {}, + "source": [ + "Note that in contrast to the `RemoveDummyArgTransformation` in the previous section, the above transformation takes no input data in its constructor. Rather, the transformation relies on the workings of the `Scheduler`, which passes the data (in this case, a variable named `newname`) to the transformation via a keyword argument `item`. The data can be accessed from the dictionary `item.config`, which (inside the transformation) corresponds to the relevant part of a `SchedulerConfig` object.\n", + "\n", + "Passing data to transformations using this mechanism can be thought of as an alternative way of passing data to transformations (as opposed to passing the data to the constructor of the transformation as in the previous section)." + ] + }, + { + "cell_type": "markdown", + "id": "1bebc0b0-49a6-4aa0-978a-cee34472db8f", + "metadata": {}, + "source": [ + "To actually define what `item.config` above should (eventually) contain, we need to construct the `SchedulerConfig`:" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "a18497d8-a43d-4c3a-91b8-eec66b244599", + "metadata": {}, + "outputs": [], + "source": [ + "from loki import SchedulerConfig\n", + "# The configuration for subroutine 'depth2' and 'wrapper':\n", + "routine_specific_config = {\n", + " 'depth2': {'newname': 'depth2_modified'}, # For 'depth2', 'newname' is 'depth2_modified'\n", + " 'wrapper': {'newname': 'i_am_the_wrapper', 'some_other_data': [1, 2, 3]} # For 'wrapper2', 'newname' is 'i_am_the_wrapper'.\n", + "}\n", + "# The 'base configuration' (for every other subroutine, and overrided by `routine_specific_config`):\n", + "default_config = {\n", + " 'expand': True,\n", + " 'newname': None # By default, 'newname' is None (i.e no change is made to name)\n", + "}\n", + "# Construct SchedulerConfig.\n", + "sconfig = SchedulerConfig(routines = routine_specific_config, default = default_config)" + ] + }, + { + "cell_type": "markdown", + "id": "98d08a6b-ec90-493c-a97d-feb51f428443", + "metadata": {}, + "source": [ + "Note that in the above configuration, we have configured the wrapper routine to also receive `some_other_data` with a value of `[1, 2, 3]`, which is not used by `RenameWithSchedulerTransformation` (but could be used by some other transformation). Furthermore, the default configuration contains `expand = True`, which tells the Scheduler to (what exactly???) (TODO: I think this is going to be removed in a further version) Note that Loki also provides a way to read the scheduler configuration from a file via `SchedulerConfig.from_file`." + ] + }, + { + "cell_type": "markdown", + "id": "05b71a28-df5c-482b-86e1-ce20cae4ac66", + "metadata": {}, + "source": [ + "With the configuration defined, we may then proceed to construct a `Scheduler` object. Here, we pass the arguments:\n", + "\n", + "* `paths`: folder paths where the Fortran files should be searched for.\n", + "* `config`: The scheduler configuration." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "2437b8d0-e259-45b7-bd9a-26c6e710277d", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Loki::Scheduler] Performed initial source scan in 0.02s\n", + "[Loki::Sourcefile] Finished constructing from src/depth2.F90 in 0.03s\n", + "[Loki::Sourcefile] Finished constructing from src/depth1.F90 in 0.05s\n", + "[Loki::Sourcefile] Finished constructing from src/wrapper.F90 in 0.02s\n", + "[Loki::Scheduler] Performed full source parse in 0.10s\n" + ] + } + ], + "source": [ + "from loki import Scheduler\n", + "scheduler = Scheduler(paths = \"src\", config = sconfig)" + ] + }, + { + "cell_type": "markdown", + "id": "f86e642a-6f4d-4f62-8362-9eed41898058", + "metadata": {}, + "source": [ + "From the message above, we see that the `Scheduler` was able to locate all dependencies of 'wrapper'. Indeed, we can inspect the `items` that the `Scheduler` found:" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "30b6ca9b-fec0-4606-ace2-02919c4fdf8f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(loki.bulk.Item<#depth2>, loki.bulk.Item<#wrapper>, loki.bulk.Item<#depth1>)" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scheduler.items" + ] + }, + { + "cell_type": "markdown", + "id": "567d71bc-31d9-4a7b-8e2c-f9eac0464111", + "metadata": {}, + "source": [ + "Each item represents a work item to be processed by the `Scheduler`. We are now ready to \"bulk apply\" our transformation to the full call tree, with the following call: " + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "b687459a-66a0-4d88-9425-5c8b194875a6", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Loki::Scheduler] Applied transformation in 0.00s\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found a new name 'i_am_the_wrapper' when processing subroutine 'wrapper', renaming..\n", + "Found a new name 'depth2_modified' when processing subroutine 'depth2', renaming..\n" + ] + } + ], + "source": [ + "scheduler.process(transformation = RenameWithSchedulerTransformation())" + ] + }, + { + "cell_type": "markdown", + "id": "9162b41c-b129-4f89-acac-4470fbe00e13", + "metadata": {}, + "source": [ + "If we now inspect the names of the routines from inside the `Scheduler` object, we see that the changes were indeed made:" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "f35f0443-9433-41b7-bf9b-9343b197865b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['depth2_modified', 'i_am_the_wrapper', 'depth1']" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[item.routine.name for item in scheduler.items]" + ] + }, + { + "cell_type": "markdown", + "id": "7d471cd7-7104-4d10-9ece-f800a8f8a578", + "metadata": {}, + "source": [ + "Note that had we defined further transformations, we could process them in sequence by repeatedly calling `scheduler.process` for each transformation, effectively creating a \"transformation pipeline\". Finally, note that it is possibly to mix different ways of passing data to transformations, depending on the use case. It is for example possible to pass some of the transformation data via `SchedulerConfig`, and the rest via the constructors of transformations." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/example/src/depth1.F90 b/example/src/depth1.F90 new file mode 100644 index 000000000..628e74c3f --- /dev/null +++ b/example/src/depth1.F90 @@ -0,0 +1,39 @@ +subroutine depth1(h_start, h_end, h_dim, v_start, v_dim, array) + implicit none + + ! Arguments. + integer, intent(in) :: h_start + integer, intent(in) :: h_end + integer, intent(in) :: h_dim + integer, intent(in) :: v_start + integer, intent(in) :: v_dim + real, intent(inout) :: array(h_dim, v_dim) + + ! Non-array local variables. + integer :: v_index + integer :: h_index + + ! Temporary arrays. + real :: tmp1(h_dim, v_dim) + + do v_index = v_start, v_dim + do h_index = h_start, h_end + tmp1(h_index, v_index) = exp(log(real(h_index)) + log(real(v_index)) - 1.0) + end do + end do + + do v_index = v_start, v_dim + do h_index = h_start, h_end + array(h_index, v_index) = exp(tmp1(h_index, v_index) + 0.25) + end do + end do + + call depth2(h_start, h_end, h_dim, v_start, v_dim, array) + + do v_index = v_start, v_dim + do h_index = h_start, h_end + array(h_index, v_index) = log(tmp1(h_index, v_index)) + array(h_index, v_index) * 2.0 + end do + end do + +end subroutine depth1 diff --git a/example/src/depth2.F90 b/example/src/depth2.F90 new file mode 100644 index 000000000..c0ab7e6d8 --- /dev/null +++ b/example/src/depth2.F90 @@ -0,0 +1,35 @@ +subroutine depth2(h_start, h_end, h_dim, v_start, v_dim, array) + implicit none + integer, intent(in) :: h_start + integer, intent(in) :: h_end + integer, intent(in) :: h_dim + integer, intent(in) :: v_start + integer, intent(in) :: v_dim + real, intent(inout) :: array(h_dim, v_dim) + + ! Non-array local variables. + integer :: v_index + integer :: h_index + real :: val + + ! Temporary arrays. + real :: tmp2(h_dim, v_dim) + + val = 1.0 + call contained(val) + tmp2 = 2.0 + val + tmp2(h_dim, v_dim) = -1.0 + + do v_index = v_start, v_dim + do h_index = h_start, h_end + array(h_index, v_index) = array(h_index, v_index) + tmp2(h_index, v_index) + end do + end do +contains + + subroutine contained(x) + real, intent(inout) :: x + x = x * 2.0 + end subroutine contained + +end subroutine depth2 diff --git a/example/src/wrapper.F90 b/example/src/wrapper.F90 new file mode 100644 index 000000000..37abbe591 --- /dev/null +++ b/example/src/wrapper.F90 @@ -0,0 +1,20 @@ +subroutine wrapper + implicit none + + integer :: h_start = 1 + integer, parameter :: h_dim = 10 + integer :: h_end + integer :: v_start = 1 + integer, parameter :: v_dim = 20 + integer, parameter :: block_size = 30 + integer :: block_index + + real :: arr(h_dim, v_dim, block_dim) + + h_end = h_dim + do block_index = 1, block_size + call depth1(h_start, h_end, h_dim, v_start, v_dim, & + arr(:, :, block_index)) + end do + print *, "Sum of array is ", sum(arr) +end subroutine wrapper