diff --git a/dace/frontend/fortran/ast_components.py b/dace/frontend/fortran/ast_components.py index ab0aa9c777..c208457c85 100644 --- a/dace/frontend/fortran/ast_components.py +++ b/dace/frontend/fortran/ast_components.py @@ -488,18 +488,29 @@ def parse_shape_specification(self, dim: f03.Explicit_Shape_Spec, size: List[FAS #now to add the dimension to the size list after processing it if necessary size.append(self.create_ast(dim_expr)) offset.append(1) + # Here we support arrays that have size declaration - with initial offset. elif len(dim_expr) == 2: # extract offets - for expr in dim_expr: - if not isinstance(expr, f03.Int_Literal_Constant): - raise TypeError("Array offsets must be constant expressions!") - offset.append(int(dim_expr[0].tostr())) - - fortran_size = int(dim_expr[1].tostr()) - int(dim_expr[0].tostr()) + 1 - fortran_ast_size = f03.Int_Literal_Constant(str(fortran_size)) - - size.append(self.create_ast(fortran_ast_size)) + if isinstance(dim_expr[0], f03.Int_Literal_Constant): + #raise TypeError("Array offsets must be constant expressions!") + offset.append(int(dim_expr[0].tostr())) + else: + expr = self.create_ast(dim_expr[0]) + offset.append(expr) + + fortran_size = ast_internal_classes.BinOp_Node( + lval=self.create_ast(dim_expr[1]), + rval=self.create_ast(dim_expr[0]), + op="-", + type="INTEGER" + ) + size.append(ast_internal_classes.BinOp_Node( + lval=fortran_size, + rval=ast_internal_classes.Int_Literal_Node(value=str(1)), + op="+", + type="INTEGER") + ) else: raise TypeError("Array dimension must be at most two expressions") diff --git a/dace/frontend/fortran/ast_transforms.py b/dace/frontend/fortran/ast_transforms.py index 57508d6d90..9c81a43c61 100644 --- a/dace/frontend/fortran/ast_transforms.py +++ b/dace/frontend/fortran/ast_transforms.py @@ -467,6 +467,10 @@ def visit_Execution_Part_Node(self, node: ast_internal_classes.Execution_Part_No variable = self.scope_vars.get_var(child.parent, var_name) offset = variable.offsets[idx] + # it can be a symbol - Name_Node - or a value + if not isinstance(offset, ast_internal_classes.Name_Node): + offset = ast_internal_classes.Int_Literal_Node(value=str(offset)) + newbody.append( ast_internal_classes.BinOp_Node( op="=", @@ -474,7 +478,7 @@ def visit_Execution_Part_Node(self, node: ast_internal_classes.Execution_Part_No rval=ast_internal_classes.BinOp_Node( op="-", lval=i, - rval=ast_internal_classes.Int_Literal_Node(value=str(offset)), + rval=offset, line_number=child.line_number), line_number=child.line_number)) else: @@ -752,7 +756,11 @@ def par_Decl_Range_Finder(node: ast_internal_classes.Array_Subscript_Node, lower_boundary = None if offsets[idx] != 1: - lower_boundary = ast_internal_classes.Int_Literal_Node(value=str(offsets[idx])) + # support symbols and integer literals + if isinstance(offsets[idx], ast_internal_classes.Name_Node): + lower_boundary = offsets[idx] + else: + lower_boundary = ast_internal_classes.Int_Literal_Node(value=str(offsets[idx])) else: lower_boundary = ast_internal_classes.Int_Literal_Node(value="1") @@ -765,10 +773,17 @@ def par_Decl_Range_Finder(node: ast_internal_classes.Array_Subscript_Node, But since the generated loop has `<=` condition, we need to subtract 1. """ if offsets[idx] != 1: + + # support symbols and integer literals + if isinstance(offsets[idx], ast_internal_classes.Name_Node): + offset = offsets[idx] + else: + offset = ast_internal_classes.Int_Literal_Node(value=str(offsets[idx])) + upper_boundary = ast_internal_classes.BinOp_Node( lval=upper_boundary, op="+", - rval=ast_internal_classes.Int_Literal_Node(value=str(offsets[idx])) + rval=offset ) upper_boundary = ast_internal_classes.BinOp_Node( lval=upper_boundary, diff --git a/dace/sdfg/propagation.py b/dace/sdfg/propagation.py index f048389421..9e4f44413b 100644 --- a/dace/sdfg/propagation.py +++ b/dace/sdfg/propagation.py @@ -1103,7 +1103,15 @@ def propagate_memlets_nested_sdfg(parent_sdfg, parent_state, nsdfg_node): if internal_memlet is None: continue try: - iedge.data = unsqueeze_memlet(internal_memlet, iedge.data, True) + ext_desc = parent_sdfg.arrays[iedge.data.data] + int_desc = sdfg.arrays[iedge.dst_conn] + iedge.data = unsqueeze_memlet( + internal_memlet, + iedge.data, + True, + internal_offset=int_desc.offset, + external_offset=ext_desc.offset + ) # If no appropriate memlet found, use array dimension for i, (rng, s) in enumerate(zip(internal_memlet.subset, parent_sdfg.arrays[iedge.data.data].shape)): if rng[1] + 1 == s: @@ -1123,7 +1131,15 @@ def propagate_memlets_nested_sdfg(parent_sdfg, parent_state, nsdfg_node): if internal_memlet is None: continue try: - oedge.data = unsqueeze_memlet(internal_memlet, oedge.data, True) + ext_desc = parent_sdfg.arrays[oedge.data.data] + int_desc = sdfg.arrays[oedge.src_conn] + oedge.data = unsqueeze_memlet( + internal_memlet, + oedge.data, + True, + internal_offset=int_desc.offset, + external_offset=ext_desc.offset + ) # If no appropriate memlet found, use array dimension for i, (rng, s) in enumerate(zip(internal_memlet.subset, parent_sdfg.arrays[oedge.data.data].shape)): if rng[1] + 1 == s: diff --git a/dace/transformation/interstate/sdfg_nesting.py b/dace/transformation/interstate/sdfg_nesting.py index 7b64e49869..73aac79ea5 100644 --- a/dace/transformation/interstate/sdfg_nesting.py +++ b/dace/transformation/interstate/sdfg_nesting.py @@ -508,14 +508,24 @@ def apply(self, state: SDFGState, sdfg: SDFG): if (edge not in modified_edges and edge.data.data == node.data): for e in state.memlet_tree(edge): if e._data.get_dst_subset(e, state): - new_memlet = helpers.unsqueeze_memlet(e.data, outer_edge.data, use_dst_subset=True) + offset = sdfg.arrays[e.data.data].offset + new_memlet = helpers.unsqueeze_memlet(e.data, + outer_edge.data, + use_dst_subset=True, + internal_offset=offset, + external_offset=offset) e._data.dst_subset = new_memlet.subset # NOTE: Node is source for edge in state.out_edges(node): if (edge not in modified_edges and edge.data.data == node.data): for e in state.memlet_tree(edge): if e._data.get_src_subset(e, state): - new_memlet = helpers.unsqueeze_memlet(e.data, outer_edge.data, use_src_subset=True) + offset = sdfg.arrays[e.data.data].offset + new_memlet = helpers.unsqueeze_memlet(e.data, + outer_edge.data, + use_src_subset=True, + internal_offset=offset, + external_offset=offset) e._data.src_subset = new_memlet.subset # If source/sink node is not connected to a source/destination access @@ -624,10 +634,17 @@ def _modify_access_to_access(self, state.out_edges_by_connector(nsdfg_node, inner_data)) # Create memlet by unsqueezing both w.r.t. src and # dst subsets - in_memlet = helpers.unsqueeze_memlet(inner_edge.data, top_edge.data, use_src_subset=True) + offset = state.parent.arrays[top_edge.data.data].offset + in_memlet = helpers.unsqueeze_memlet(inner_edge.data, + top_edge.data, + use_src_subset=True, + internal_offset=offset, + external_offset=offset) out_memlet = helpers.unsqueeze_memlet(inner_edge.data, matching_edge.data, - use_dst_subset=True) + use_dst_subset=True, + internal_offset=offset, + external_offset=offset) new_memlet = in_memlet new_memlet.other_subset = out_memlet.subset @@ -650,10 +667,18 @@ def _modify_access_to_access(self, state.out_edges_by_connector(nsdfg_node, inner_data)) # Create memlet by unsqueezing both w.r.t. src and # dst subsets - in_memlet = helpers.unsqueeze_memlet(inner_edge.data, top_edge.data, use_src_subset=True) + offset = state.parent.arrays[top_edge.data.data].offset + in_memlet = helpers.unsqueeze_memlet(inner_edge.data, + top_edge.data, + use_src_subset=True, + internal_offset=offset, + external_offset=offset) out_memlet = helpers.unsqueeze_memlet(inner_edge.data, matching_edge.data, - use_dst_subset=True) + use_dst_subset=True, + internal_offset=offset, + external_offset=offset) + new_memlet = in_memlet new_memlet.other_subset = out_memlet.subset @@ -688,7 +713,11 @@ def _modify_memlet_path( if inner_edge in edges_to_ignore: new_memlet = inner_edge.data else: - new_memlet = helpers.unsqueeze_memlet(inner_edge.data, top_edge.data) + offset = state.parent.arrays[top_edge.data.data].offset + new_memlet = helpers.unsqueeze_memlet(inner_edge.data, + top_edge.data, + internal_offset=offset, + external_offset=offset) if inputs: if inner_edge.dst in inner_to_outer: dst = inner_to_outer[inner_edge.dst] @@ -707,15 +736,19 @@ def _modify_memlet_path( mtree = state.memlet_tree(new_edge) # Modify all memlets going forward/backward - def traverse(mtree_node): + def traverse(mtree_node, state, nstate): result.add(mtree_node.edge) - mtree_node.edge._data = helpers.unsqueeze_memlet(mtree_node.edge.data, top_edge.data) + offset = state.parent.arrays[top_edge.data.data].offset + mtree_node.edge._data = helpers.unsqueeze_memlet(mtree_node.edge.data, + top_edge.data, + internal_offset=offset, + external_offset=offset) for child in mtree_node.children: - traverse(child) + traverse(child, state, nstate) result.add(new_edge) for child in mtree.children: - traverse(child) + traverse(child, state, nstate) return result @@ -1035,7 +1068,8 @@ def _check_cand(candidates, outer_edges): # If there are any symbols here that are not defined # in "defined_symbols" - missing_symbols = (memlet.get_free_symbols_by_indices(list(indices), list(indices)) - set(nsdfg.symbol_mapping.keys())) + missing_symbols = (memlet.get_free_symbols_by_indices(list(indices), list(indices)) - + set(nsdfg.symbol_mapping.keys())) if missing_symbols: ignore.add(cname) continue @@ -1044,10 +1078,13 @@ def _check_cand(candidates, outer_edges): _check_cand(out_candidates, state.out_edges_by_connector) # Return result, filtering out the states - return ({k: (dc(v), ind) - for k, (v, _, ind) in in_candidates.items() - if k not in ignore}, {k: (dc(v), ind) - for k, (v, _, ind) in out_candidates.items() if k not in ignore}) + return ({ + k: (dc(v), ind) + for k, (v, _, ind) in in_candidates.items() if k not in ignore + }, { + k: (dc(v), ind) + for k, (v, _, ind) in out_candidates.items() if k not in ignore + }) def can_be_applied(self, graph: SDFGState, expr_index: int, sdfg: SDFG, permissive: bool = False): nsdfg = self.nsdfg @@ -1070,7 +1107,17 @@ def _offset_refine(torefine: Dict[str, Tuple[Memlet, Set[int]]], outer_edge = next(iter(outer_edges(nsdfg_node, aname))) except StopIteration: continue - new_memlet = helpers.unsqueeze_memlet(refine, outer_edge.data) + + if isinstance(outer_edge.dst, nodes.NestedSDFG): + conn = outer_edge.dst_conn + else: + conn = outer_edge.src_conn + int_desc = nsdfg.arrays[conn] + ext_desc = sdfg.arrays[outer_edge.data.data] + new_memlet = helpers.unsqueeze_memlet(refine, + outer_edge.data, + internal_offset=int_desc.offset, + external_offset=ext_desc.offset) outer_edge.data.subset = subsets.Range([ ns if i in indices else os for i, (os, ns) in enumerate(zip(outer_edge.data.subset, new_memlet.subset)) diff --git a/tests/fortran/array_attributes_test.py b/tests/fortran/array_attributes_test.py index af433905bc..0c9bc3e3a0 100644 --- a/tests/fortran/array_attributes_test.py +++ b/tests/fortran/array_attributes_test.py @@ -39,6 +39,45 @@ def test_fortran_frontend_array_attribute_no_offset(): # offset -1 is already added assert a[i-1] == i * 2 +def test_fortran_frontend_array_attribute_no_offset_symbol(): + """ + Tests that the Fortran frontend can parse array accesses and that the accessed indices are correct. + """ + test_string = """ + PROGRAM index_offset_test + implicit none + integer, parameter :: arrsize = 5 + double precision, dimension(arrsize) :: d + CALL index_test_function(d,arrsize) + end + + SUBROUTINE index_test_function(d, arrsize) + integer :: arrsize + double precision, dimension(arrsize) :: d + + do i=1,arrsize + d(i) = i * 2.0 + end do + + END SUBROUTINE index_test_function + """ + sdfg = fortran_parser.create_sdfg_from_string(test_string, "index_offset_test") + sdfg.simplify(verbose=True) + sdfg.compile() + + assert len(sdfg.data('d').shape) == 1 + from dace.symbolic import symbol + assert isinstance(sdfg.data('d').shape[0], symbol) + assert len(sdfg.data('d').offset) == 1 + assert sdfg.data('d').offset[0] == -1 + + size = 10 + a = np.full([size], 42, order="F", dtype=np.float64) + sdfg(d=a, arrsize=size) + for i in range(1,size): + # offset -1 is already added + assert a[i-1] == i * 2 + def test_fortran_frontend_array_attribute_offset(): """ Tests that the Fortran frontend can parse array accesses and that the accessed indices are correct. @@ -74,6 +113,89 @@ def test_fortran_frontend_array_attribute_offset(): # offset -1 is already added assert a[i-1] == i * 2 +def test_fortran_frontend_array_attribute_offset_symbol(): + """ + Tests that the Fortran frontend can parse array accesses and that the accessed indices are correct. + """ + test_string = """ + PROGRAM index_offset_test + implicit none + integer :: arrsize + double precision, dimension(arrsize:arrsize+4) :: d + CALL index_test_function(d, arrsize) + end + + SUBROUTINE index_test_function(d, arrsize) + integer :: arrsize + double precision, dimension(arrsize:arrsize+4) :: d + + do i=arrsize, arrsize+4 + d(i) = i * 2.0 + end do + + END SUBROUTINE index_test_function + """ + sdfg = fortran_parser.create_sdfg_from_string(test_string, "index_offset_test") + sdfg.simplify(verbose=True) + sdfg.compile() + + assert len(sdfg.data('d').shape) == 1 + assert sdfg.data('d').shape[0] == 5 + assert len(sdfg.data('d').offset) == 1 + assert sdfg.data('d').offset[0] == -1 + + arrsize=50 + a = np.full([arrsize+10], 42, order="F", dtype=np.float64) + sdfg(d=a,arrsize=arrsize) + for i in range(arrsize,arrsize+4): + # offset -1 is already added + assert a[i-1] == i * 2 + +def test_fortran_frontend_array_attribute_offset_symbol2(): + """ + Tests that the Fortran frontend can parse array accesses and that the accessed indices are correct. + Compared to the previous one, this one should prevent simplification from removing symbols + """ + test_string = """ + PROGRAM index_offset_test + implicit none + integer, parameter :: arrsize = 5 + integer, parameter :: arrsize2 = 5 + double precision, dimension(arrsize:arrsize2) :: d + CALL index_test_function(d, arrsize, arrsize2) + end + + SUBROUTINE index_test_function(d, arrsize, arrsize2) + integer :: arrsize + integer :: arrsize2 + double precision, dimension(arrsize:arrsize2) :: d + + do i=arrsize, arrsize2 + d(i) = i * 2.0 + end do + + END SUBROUTINE index_test_function + """ + sdfg = fortran_parser.create_sdfg_from_string(test_string, "index_offset_test") + sdfg.simplify(verbose=True) + sdfg.compile() + + from dace.symbolic import evaluate + + arrsize=50 + arrsize2=54 + assert len(sdfg.data('d').shape) == 1 + assert evaluate(sdfg.data('d').shape[0], {'arrsize': arrsize, 'arrsize2': arrsize2}) == 5 + assert len(sdfg.data('d').offset) == 1 + assert sdfg.data('d').offset[0] == -1 + + a = np.full([arrsize+10], 42, order="F", dtype=np.float64) + sdfg(d=a,arrsize=arrsize,arrsize2=arrsize2) + for i in range(arrsize,arrsize2): + # offset -1 is already added + assert a[i-1] == i * 2 + + def test_fortran_frontend_array_offset(): """ Tests that the Fortran frontend can parse array accesses and that the accessed indices are correct. @@ -109,9 +231,57 @@ def test_fortran_frontend_array_offset(): # offset -1 is already added assert a[i-1] == i * 2 +def test_fortran_frontend_array_offset_symbol(): + """ + Tests that the Fortran frontend can parse array accesses and that the accessed indices are correct. + Compared to the previous one, this one should prevent simplification from removing symbols + """ + test_string = """ + PROGRAM index_offset_test + implicit none + integer, parameter :: arrsize = 5 + integer, parameter :: arrsize2 = 5 + double precision :: d(arrsize:arrsize2) + CALL index_test_function(d, arrsize, arrsize2) + end + + SUBROUTINE index_test_function(d, arrsize, arrsize2) + integer :: arrsize + integer :: arrsize2 + double precision :: d(arrsize:arrsize2) + + do i=arrsize, arrsize2 + d(i) = i * 2.0 + end do + + END SUBROUTINE index_test_function + """ + sdfg = fortran_parser.create_sdfg_from_string(test_string, "index_offset_test") + sdfg.simplify(verbose=True) + sdfg.compile() + + from dace.symbolic import evaluate + + arrsize=50 + arrsize2=54 + assert len(sdfg.data('d').shape) == 1 + assert evaluate(sdfg.data('d').shape[0], {'arrsize': arrsize, 'arrsize2': arrsize2}) == 5 + assert len(sdfg.data('d').offset) == 1 + assert sdfg.data('d').offset[0] == -1 + + a = np.full([arrsize+10], 42, order="F", dtype=np.float64) + sdfg(d=a,arrsize=arrsize,arrsize2=arrsize2) + for i in range(arrsize,arrsize2): + # offset -1 is already added + assert a[i-1] == i * 2 + if __name__ == "__main__": test_fortran_frontend_array_offset() test_fortran_frontend_array_attribute_no_offset() test_fortran_frontend_array_attribute_offset() + test_fortran_frontend_array_attribute_no_offset_symbol() + test_fortran_frontend_array_attribute_offset_symbol() + test_fortran_frontend_array_attribute_offset_symbol2() + test_fortran_frontend_array_offset_symbol() diff --git a/tests/fortran/offset_normalizer_test.py b/tests/fortran/offset_normalizer_test.py index b4138c1cac..21bc3b8a31 100644 --- a/tests/fortran/offset_normalizer_test.py +++ b/tests/fortran/offset_normalizer_test.py @@ -2,7 +2,7 @@ import numpy as np -from dace.frontend.fortran import ast_transforms, fortran_parser +from dace.frontend.fortran import ast_internal_classes, ast_transforms, fortran_parser def test_fortran_frontend_offset_normalizer_1d(): """ @@ -48,6 +48,60 @@ def test_fortran_frontend_offset_normalizer_1d(): for i in range(0,5): assert a[i] == (50+i)* 2 +def test_fortran_frontend_offset_normalizer_1d_symbol(): + """ + Tests that the Fortran frontend can parse array accesses and that the accessed indices are correct. + """ + test_string = """ + PROGRAM index_offset_test + implicit none + integer, parameter :: arrsize = 5 + integer, parameter :: arrsize2 = 5 + double precision :: d(arrsize:arrsize2) + CALL index_test_function(d, arrsize, arrsize2) + end + + SUBROUTINE index_test_function(d, arrsize, arrsize2) + integer :: arrsize + integer :: arrsize2 + double precision :: d(arrsize:arrsize2) + + do i=arrsize,arrsize2 + d(i) = i * 2.0 + end do + + END SUBROUTINE index_test_function + """ + + # Test to verify that offset is normalized correctly + ast, own_ast = fortran_parser.create_ast_from_string(test_string, "index_offset_test", True, True) + + for subroutine in ast.subroutine_definitions: + + loop = subroutine.execution_part.execution[1] + idx_assignment = loop.body.execution[1] + assert isinstance(idx_assignment.rval.rval, ast_internal_classes.Name_Node) + assert idx_assignment.rval.rval.name == "arrsize" + + # Now test to verify it executes correctly + + sdfg = fortran_parser.create_sdfg_from_string(test_string, "index_offset_test", True) + sdfg.simplify(verbose=True) + sdfg.compile() + + from dace.symbolic import evaluate + arrsize=50 + arrsize2=54 + assert len(sdfg.data('d').shape) == 1 + assert evaluate(sdfg.data('d').shape[0], {'arrsize': arrsize, 'arrsize2': arrsize2}) == 5 + + arrsize=50 + arrsize2=54 + a = np.full([arrsize2-arrsize+1], 42, order="F", dtype=np.float64) + sdfg(d=a, arrsize=arrsize, arrsize2=arrsize2) + for i in range(0, arrsize2 - arrsize + 1): + assert a[i] == (50+i)* 2 + def test_fortran_frontend_offset_normalizer_2d(): """ Tests that the Fortran frontend can parse array accesses and that the accessed indices are correct. @@ -103,6 +157,78 @@ def test_fortran_frontend_offset_normalizer_2d(): for j in range(0,3): assert a[i, j] == (50+i) * 2 + 3 * (7 + j) +def test_fortran_frontend_offset_normalizer_2d_symbol(): + """ + Tests that the Fortran frontend can parse array accesses and that the accessed indices are correct. + """ + test_string = """ + PROGRAM index_offset_test + implicit none + integer, parameter :: arrsize = 3 + integer, parameter :: arrsize2 = 4 + integer, parameter :: arrsize3 = 5 + integer, parameter :: arrsize4 = 6 + double precision, dimension(arrsize:arrsize2,arrsize3:arrsize4) :: d + CALL index_test_function(d, arrsize, arrsize2, arrsize3, arrsize4) + end + + SUBROUTINE index_test_function(d, arrsize, arrsize2, arrsize3, arrsize4) + integer :: arrsize + integer :: arrsize2 + integer :: arrsize3 + integer :: arrsize4 + double precision, dimension(arrsize:arrsize2,arrsize3:arrsize4) :: d + + do i=arrsize, arrsize2 + do j=arrsize3, arrsize4 + d(i, j) = i * 2.0 + 3 * j + end do + end do + + END SUBROUTINE index_test_function + """ + + # Test to verify that offset is normalized correctly + ast, own_ast = fortran_parser.create_ast_from_string(test_string, "index_offset_test", True, True) + + for subroutine in ast.subroutine_definitions: + + loop = subroutine.execution_part.execution[1] + nested_loop = loop.body.execution[1] + + idx = nested_loop.body.execution[1] + assert idx.lval.name == 'tmp_index_0' + assert isinstance(idx.rval.rval, ast_internal_classes.Name_Node) + assert idx.rval.rval.name == "arrsize" + + idx2 = nested_loop.body.execution[3] + assert idx2.lval.name == 'tmp_index_1' + assert isinstance(idx2.rval.rval, ast_internal_classes.Name_Node) + assert idx2.rval.rval.name == "arrsize3" + + # Now test to verify it executes correctly + + sdfg = fortran_parser.create_sdfg_from_string(test_string, "index_offset_test", True) + sdfg.simplify(verbose=True) + sdfg.compile() + + from dace.symbolic import evaluate + values = { + 'arrsize': 50, + 'arrsize2': 54, + 'arrsize3': 7, + 'arrsize4': 9 + } + assert len(sdfg.data('d').shape) == 2 + assert evaluate(sdfg.data('d').shape[0], values) == 5 + assert evaluate(sdfg.data('d').shape[1], values) == 3 + + a = np.full([5,3], 42, order="F", dtype=np.float64) + sdfg(d=a, **values) + for i in range(0,5): + for j in range(0,3): + assert a[i, j] == (50+i) * 2 + 3 * (7 + j) + def test_fortran_frontend_offset_normalizer_2d_arr2loop(): """ Tests that the Fortran frontend can parse array accesses and that the accessed indices are correct. @@ -157,8 +283,81 @@ def test_fortran_frontend_offset_normalizer_2d_arr2loop(): for j in range(0,3): assert a[i, j] == (50 + i) * 2 +def test_fortran_frontend_offset_normalizer_2d_arr2loop_symbol(): + """ + Tests that the Fortran frontend can parse array accesses and that the accessed indices are correct. + """ + test_string = """ + PROGRAM index_offset_test + implicit none + integer, parameter :: arrsize = 3 + integer, parameter :: arrsize2 = 4 + integer, parameter :: arrsize3 = 5 + integer, parameter :: arrsize4 = 6 + double precision, dimension(arrsize:arrsize2,arrsize3:arrsize4) :: d + CALL index_test_function(d, arrsize, arrsize2, arrsize3, arrsize4) + end + + SUBROUTINE index_test_function(d, arrsize, arrsize2, arrsize3, arrsize4) + integer :: arrsize + integer :: arrsize2 + integer :: arrsize3 + integer :: arrsize4 + double precision, dimension(arrsize:arrsize2,arrsize3:arrsize4) :: d + + do i=arrsize,arrsize2 + d(i, :) = i * 2.0 + end do + + END SUBROUTINE index_test_function + """ + + # Test to verify that offset is normalized correctly + ast, own_ast = fortran_parser.create_ast_from_string(test_string, "index_offset_test", True, True) + + for subroutine in ast.subroutine_definitions: + + loop = subroutine.execution_part.execution[1] + nested_loop = loop.body.execution[1] + + idx = nested_loop.body.execution[1] + assert idx.lval.name == 'tmp_index_0' + assert isinstance(idx.rval.rval, ast_internal_classes.Name_Node) + assert idx.rval.rval.name == "arrsize" + + idx2 = nested_loop.body.execution[3] + assert idx2.lval.name == 'tmp_index_1' + assert isinstance(idx2.rval.rval, ast_internal_classes.Name_Node) + assert idx2.rval.rval.name == "arrsize3" + + # Now test to verify it executes correctly with no normalization + + sdfg = fortran_parser.create_sdfg_from_string(test_string, "index_offset_test", True) + sdfg.simplify(verbose=True) + sdfg.compile() + + from dace.symbolic import evaluate + values = { + 'arrsize': 50, + 'arrsize2': 54, + 'arrsize3': 7, + 'arrsize4': 9 + } + assert len(sdfg.data('d').shape) == 2 + assert evaluate(sdfg.data('d').shape[0], values) == 5 + assert evaluate(sdfg.data('d').shape[1], values) == 3 + + a = np.full([5,3], 42, order="F", dtype=np.float64) + sdfg(d=a, **values) + for i in range(0,5): + for j in range(0,3): + assert a[i, j] == (50 + i) * 2 + if __name__ == "__main__": test_fortran_frontend_offset_normalizer_1d() test_fortran_frontend_offset_normalizer_2d() test_fortran_frontend_offset_normalizer_2d_arr2loop() + test_fortran_frontend_offset_normalizer_1d_symbol() + test_fortran_frontend_offset_normalizer_2d_symbol() + test_fortran_frontend_offset_normalizer_2d_arr2loop_symbol() diff --git a/tests/memlet_propagation_test.py b/tests/memlet_propagation_test.py index f90834cbb7..b371adba47 100644 --- a/tests/memlet_propagation_test.py +++ b/tests/memlet_propagation_test.py @@ -103,9 +103,48 @@ def sparse(A: dace.float32[M, N], ind: dace.int32[M, N]): raise RuntimeError('Expected subset of outer out memlet to be [0:M, 0:N], found ' + str(outer_out.subset)) +def test_memlet_propagation_with_offsets(): + code = """ + PROGRAM foo + IMPLICIT NONE + REAL INP1(NBLOCKS, KLEV) + INTEGER, PARAMETER :: KLEV = 137 + INTEGER, PARAMETER :: NBLOCKS = 8 + + + CALL foo_test_function(NBLOCKS, KLEV, INP1) + + END PROGRAM + + SUBROUTINE foo_test_function(NBLOCKS, KLEV, INP1) + INTEGER, PARAMETER :: KLEV = 137 + INTEGER, PARAMETER :: NBLOCKS = 1 + REAL INP1(NBLOCKS, KLEV) + + DO JN=1,NBLOCKS + DO JK=1,KLEV + INP1(JN, JK) = (JN-1) * KLEV + (JK-1) + ENDDO + ENDDO + END SUBROUTINE foo_test_function + """ + + from dace.frontend.fortran import fortran_parser + from dace.transformation.interstate import LoopToMap + sdfg = fortran_parser.create_sdfg_from_string(code, "test_loop_map_parallel") + + # Convert into map(NestedSDFG(map)) + sdfg.simplify() + sdfg.apply_transformations_repeated([LoopToMap]) + + # Offsets of arrays (-1, -1) must be propagated through NestedSDFG correctly + propagate_memlets_sdfg(sdfg) + sdfg.validate() + if __name__ == '__main__': test_conditional() test_conditional_nested() test_runtime_conditional() test_nsdfg_memlet_propagation_with_one_sparse_dimension() + test_memlet_propagation_with_offsets()