def test_type_convert_binaryop_create(operation, op_str): '''Test that the create method in the BinaryOperation class correctly creates a BinaryOperation instance for the REAL and INT type-conversion operations.. ''' sym = DataSymbol("tmp1", REAL_SINGLE_TYPE) lhs = Reference(sym) wp_sym = DataSymbol("wp", INTEGER_SINGLE_TYPE) # Reference to a kind parameter rhs = Reference(wp_sym) binaryoperation = BinaryOperation.create(operation, lhs, rhs) assert binaryoperation._operator is operation check_links(binaryoperation, [lhs, rhs]) result = FortranWriter().binaryoperation_node(binaryoperation) assert op_str + "(tmp1, wp)" in result.lower() # Kind specified with an integer literal rhs = Literal("4", INTEGER_SINGLE_TYPE) binaryoperation = BinaryOperation.create(operation, lhs.detach(), rhs) check_links(binaryoperation, [lhs, rhs]) result = FortranWriter().binaryoperation_node(binaryoperation) assert op_str + "(tmp1, 4)" in result.lower() # Kind specified as an arithmetic expression rhs = BinaryOperation.create(BinaryOperation.Operator.ADD, Reference(wp_sym), Literal("2", INTEGER_SINGLE_TYPE)) binaryoperation = BinaryOperation.create(operation, lhs.detach(), rhs) check_links(binaryoperation, [lhs, rhs]) result = FortranWriter().binaryoperation_node(binaryoperation) assert op_str + "(tmp1, wp + 2)" in result.lower()
def test_ifblock_create(): '''Test that the create method in an IfBlock class correctly creates an IfBlock instance. ''' # Without an else clause. if_condition = Literal('true', BOOLEAN_TYPE) if_body = [Assignment.create( Reference(DataSymbol("tmp", REAL_SINGLE_TYPE)), Literal("0.0", REAL_SINGLE_TYPE)), Assignment.create( Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE)), Literal("1.0", REAL_SINGLE_TYPE))] ifblock = IfBlock.create(if_condition, if_body) if_schedule = ifblock.children[1] assert isinstance(if_schedule, Schedule) check_links(ifblock, [if_condition, if_schedule]) check_links(if_schedule, if_body) result = FortranWriter().ifblock_node(ifblock) assert result == ("if (.true.) then\n" " tmp = 0.0\n" " tmp2 = 1.0\n" "end if\n") # With an else clause. if_condition = Literal('true', BOOLEAN_TYPE) if_body = [Assignment.create( Reference(DataSymbol("tmp", REAL_SINGLE_TYPE)), Literal("0.0", REAL_SINGLE_TYPE)), Assignment.create( Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE)), Literal("1.0", REAL_SINGLE_TYPE))] else_body = [Assignment.create(Reference(DataSymbol("tmp", REAL_SINGLE_TYPE)), Literal("1.0", REAL_SINGLE_TYPE)), Assignment.create(Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE)), Literal("0.0", REAL_SINGLE_TYPE))] ifblock = IfBlock.create(if_condition, if_body, else_body) if_schedule = ifblock.children[1] assert isinstance(if_schedule, Schedule) else_schedule = ifblock.children[2] assert isinstance(else_schedule, Schedule) check_links(ifblock, [if_condition, if_schedule, else_schedule]) check_links(if_schedule, if_body) check_links(else_schedule, else_body) result = FortranWriter().ifblock_node(ifblock) assert result == ("if (.true.) then\n" " tmp = 0.0\n" " tmp2 = 1.0\n" "else\n" " tmp = 1.0\n" " tmp2 = 0.0\n" "end if\n")
def test_nemo_omp_do(fortran_reader): '''Tests if an OpenMP do directive in NEMO is handled correctly. ''' # Generate fparser2 parse tree from Fortran code. code = ''' module test contains subroutine tmp() integer :: i, a integer, dimension(:) :: b do i = 1, 20, 2 a = 2 * i b(i) = b(i) + a enddo end subroutine tmp end module test''' psyir = fortran_reader.psyir_from_source(code) schedule = psyir.children[0].children[0] # Now apply a parallel transform omp_loop = OMPLoopTrans() omp_loop.apply(schedule[0]) # By default the visitor should raise an exception because the loop # directive is not inside a parallel region fvisitor_with_checks = FortranWriter() with pytest.raises(GenerationError) as err: fvisitor_with_checks(schedule) assert ("OMPDoDirective must be inside an OMP parallel region but could " "not find an ancestor OMPParallelDirective" in str(err.value)) # Disable checks on global constraints to remove need for parallel region fvisitor = FortranWriter(check_global_constraints=False) result = fvisitor(schedule) correct = ''' !$omp do schedule(static) do i = 1, 20, 2 a = 2 * i b(i) = b(i) + a enddo !$omp end do''' assert correct in result cvisitor = CWriter(check_global_constraints=False) result = cvisitor(schedule[0]) correct = '''#pragma omp do schedule(static) { for(i=1; i<=20; i+=2) { a = (2 * i); b[i] = (b[i] + a); } }''' assert correct in result
def test_nemo_acc_kernels(default_present, expected, parser): ''' Tests that an OpenACC kernels directive is handled correctly in the NEMO API. ''' # Generate fparser2 parse tree from Fortran code. reader = FortranStringReader(NEMO_TEST_CODE) code = parser(reader) psy = PSyFactory("nemo", distributed_memory=False).create(code) nemo_sched = psy.invokes.invoke_list[0].schedule # Now apply a kernels transform ktrans = ACCKernelsTrans() options = {"default_present": default_present} ktrans.apply(nemo_sched[0], options) fvisitor = FortranWriter() result = fvisitor(nemo_sched) correct = '''!$acc kernels{0} do i = 1, 20, 2 a = 2 * i + d(i) c(i) = a b(i) = b(i) + a + c(i) enddo !$acc end kernels'''.format(expected) assert correct in result cvisitor = CWriter() with pytest.raises(VisitorError) as err: _ = cvisitor(nemo_sched[0]) assert "Unsupported node 'ACCKernelsDirective' found" in str(err.value)
def test_fw_codeblock(): '''Check the FortranWriter class codeblock method correctly prints out the Fortran code contained within it. ''' # Generate fparser2 parse tree from Fortran code. code = ( "module test\n" "contains\n" "subroutine tmp()\n" " integer :: a\n" " a=1\n" "end subroutine tmp\n" "end module test") schedule = create_schedule(code) code1 = ( "print *, 'I am a code block'\n" "print *, 'with more than one line'\n") _ = ParserFactory().create(std="f2003") reader = get_reader(code1) statements = Fortran2003.Execution_Part(reader) code_block = CodeBlock([statements], parent=schedule) schedule.addchild(code_block) # Generate Fortran from the PSyIR schedule fvisitor = FortranWriter() result = fvisitor(schedule) assert ( " a=1\n" "PRINT *, 'I am a code block'\n" " PRINT *, 'with more than one line'\n" in result)
def test_correct_binary(func, output, tmpdir): '''Check that a valid example produces the expected output when the first argument to MIN is a simple argument and when it is an expression. ''' Config.get().api = "nemo" operation = example_psyir_binary(func) writer = FortranWriter() result = writer(operation.root) assert ("subroutine min_example(arg,arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n\n" " psyir_tmp=MIN({0}, arg_1)\n\n" "end subroutine min_example\n".format(output)) in result trans = Min2CodeTrans() trans.apply(operation, operation.root.symbol_table) result = writer(operation.root) assert ("subroutine min_example(arg,arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n" " real :: res_min\n" " real :: tmp_min\n\n" " res_min={0}\n" " tmp_min=arg_1\n" " if (tmp_min < res_min) then\n" " res_min=tmp_min\n" " end if\n" " psyir_tmp=res_min\n\n" "end subroutine min_example\n".format(output)) in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def test_apply_reference_literal(): '''Check that the apply method add bounds appropriately when the config file specifies a lower bound as a reference and an upper bound as a literal. ''' _, invoke_info = get_invoke("implicit_many_dims.f90", api=API, idx=0) # Create a new config instance and load a test config file with # the bounds information set the way we want. config = Config.get(do_not_load_file=True) config.load(config_file=TEST_CONFIG) schedule = invoke_info.schedule assignment = schedule[0] array_ref = assignment.lhs trans = NemoArrayRange2LoopTrans() for index in range(4, -1, -1): range_node = array_ref.children[index] trans.apply(range_node) # Remove this config file so the next time the default one will be # loaded (in case we affect other tests) Config._instance = None writer = FortranWriter() result = writer(schedule) assert ("do idx = LBOUND(umask, 5), UBOUND(umask, 5), 1\n" " do jt = 1, UBOUND(umask, 4), 1\n" " do jk = jpk, 1, 1\n" " do jj = 1, jpj, 1\n" " do ji = jpi, 1, 1\n" " umask(ji,jj,jk,jt,idx) = vmask(ji,jj,jk,jt,idx) + 1.0\n" " enddo\n" " enddo\n" " enddo\n" " enddo\n" "enddo" in result)
def test_gocean_omp_parallel(): '''Test that an OMP PARALLEL directive in a 'classical' API (gocean here) is created correctly. ''' from psyclone.transformations import OMPParallelTrans _, invoke = get_invoke("single_invoke.f90", "gocean1.0", idx=0) omp = OMPParallelTrans() omp_sched, _ = omp.apply(invoke.schedule[0]) # Now remove the GOKern (since it's not yet supported in the # visitor pattern) and replace it with a simple assignment # TODO: #440 tracks this replace_child_with_assignment(omp_sched[0]) # omp_sched is a GOInvokeSchedule, which is not yet supported. # So only convert starting from the OMPParallelDirective fvisitor = FortranWriter() result = fvisitor(omp_sched[0]) correct = '''!$omp parallel a=b !$omp end parallel''' assert correct in result cvisitor = CWriter() # Remove newlines for easier RE matching result = cvisitor(omp_sched[0]) correct = '''#pragma omp parallel { a = b; }''' result = cvisitor(omp_sched[0]) assert correct in result
def trans(psy): '''PSyclone transformation script for the Dynamo0.3 API to make the kernel values of ndofs, nlayers and nquadrature-point sizes constant. ''' const_trans = Dynamo0p3KernelConstTrans() fortran_writer = FortranWriter() for invoke in psy.invokes.invoke_list: print("invoke '{0}'".format(invoke.name)) schedule = invoke.schedule for kernel in schedule.coded_kernels(): print(" kernel '{0}'".format(kernel.name.lower())) try: const_trans.apply(kernel, number_of_layers=NUMBER_OF_LAYERS, element_order=ELEMENT_ORDER, quadrature=CONSTANT_QUADRATURE) except TransformationError: print(" Failed to modify kernel '{0}'".format(kernel.name)) kernel_schedule = kernel.get_kernel_schedule() kern_code = fortran_writer(kernel_schedule) print(kern_code) return psy
def test_program_handler(parser): '''Test that program handler works with multiple program units of different types. ''' code = ("module a\n" "end module\n" "program b\n" "end program b\n" "subroutine c\n" "end subroutine") expected = ("module a\n" " implicit none\n\n" " contains\n\n" "end module a\n" "program b\n\n\n" "end program b\n" "subroutine c()\n\n\n" "end subroutine c\n") processor = Fparser2Reader() reader = FortranStringReader(code) parse_tree = parser(reader) psyir = processor._program_handler(parse_tree, None) # Check PSyIR nodes are being created assert isinstance(psyir, FileContainer) assert psyir.parent is None writer = FortranWriter() result = writer(psyir) assert result == expected
def test_fw_ifblock(): '''Check the FortranWriter class ifblock method correctly prints out the Fortran representation. ''' # Generate fparser2 parse tree from Fortran code. code = ("module test\n" "contains\n" "subroutine tmp(a,n)\n" " integer, intent(inout) :: n\n" " real, intent(out) :: a(n)\n" " if (n.gt.2) then\n" " n=n+1\n" " end if\n" " if (n.gt.4) then\n" " a = -1\n" " else\n" " a = 1\n" " end if\n" "end subroutine tmp\n" "end module test") schedule = create_schedule(code) # Generate Fortran from the PSyIR schedule fvisitor = FortranWriter() result = fvisitor(schedule) assert (" if (n > 2) then\n" " n=n + 1\n" " end if\n" " if (n > 4) then\n" " a=-1\n" " else\n" " a=1\n" " end if\n") in result
def test_fw_nemokern(): '''Check the FortranWriter class nemokern method prints the class information and calls any children. This method is used to output nothing for a NemoKern object and simply call its children as NemoKern is a collection of PSyIR nodes so needs no output itself. ''' # Generate fparser2 parse tree from Fortran code. code = ("module test\n" "contains\n" "subroutine tmp()\n" " integer :: a,b,c\n" " a = b/c\n" "end subroutine tmp\n" "end module test") schedule = create_schedule(code) # add a NemoKern object to the tree from psyclone.nemo import NemoKern nemo_kern = NemoKern(schedule.children, None, parent=schedule) schedule.children = [nemo_kern] # Generate Fortran from the PSyIR schedule fvisitor = FortranWriter() result = fvisitor(schedule) assert (" subroutine tmp()\n" " integer(i_def) :: a\n" " integer(i_def) :: b\n" " integer(i_def) :: c\n" "\n" " a=b / c\n" "\n" " end subroutine tmp\n") in result
def test_acc_data_region(parser): ''' Test that an ACCDataDirective node generates the expected code. ''' # Generate fparser2 parse tree from Fortran code. reader = FortranStringReader(NEMO_TEST_CODE) code = parser(reader) psy = PSyFactory("nemo", distributed_memory=False).create(code) sched = psy.invokes.invoke_list[0].schedule dtrans = ACCDataTrans() dtrans.apply(sched) fvisitor = FortranWriter() result = fvisitor(sched) assert ("!$acc data copyin(d) copyout(c) copy(b)\n" "do i = 1, 20, 2\n" in result) assert ("enddo\n" "!$acc end data\n" in result) assigns = sched.walk(Assignment) # Remove the read from array 'd' assigns[0].detach() result = fvisitor(sched) assert ("!$acc data copyout(c) copy(b)\n" "do i = 1, 20, 2\n" in result) # Remove the readwrite of array 'b' assigns[2].detach() result = fvisitor(sched) assert ("!$acc data copyout(c)\n" "do i = 1, 20, 2\n" in result)
def test_gocean_acc_parallel(): '''Test that an ACC PARALLEL directive in a 'classical' API (gocean here) is created correctly. ''' _, invoke = get_invoke("single_invoke.f90", "gocean1.0", idx=0, dist_mem=False) ptrans = ACCParallelTrans() ptrans.apply(invoke.schedule[0]) # Now remove the GOKern (since it's not yet supported in the # visitor pattern) and replace it with a simple assignment # TODO: #440 tracks this replace_child_with_assignment(invoke.schedule[0].dir_body) # omp_sched is a GOInvokeSchedule, which is not yet supported. # So only convert starting from the OMPParallelDirective. Also, disable # node validation so as to avoid the need for a data region. fvisitor = FortranWriter(check_global_constraints=False) result = fvisitor(invoke.schedule[0]) correct = '''!$acc begin parallel default(present) a = b !$acc end parallel''' assert correct in result cvisitor = CWriter(check_global_constraints=False) with pytest.raises(VisitorError) as err: _ = cvisitor(invoke.schedule[0]) assert "Unsupported node 'ACCParallelDirective' found" in str(err.value)
def test_nemo_acc_parallel(parser): '''Tests that an OpenACC parallel directive in NEMO is handled correctly. ''' # Generate fparser2 parse tree from Fortran code. reader = FortranStringReader(NEMO_TEST_CODE) code = parser(reader) psy = PSyFactory("nemo", distributed_memory=False).create(code) nemo_sched = psy.invokes.invoke_list[0].schedule # Now apply an ACC parallel transform dtrans = ACCDataTrans() ktrans = ACCParallelTrans() ktrans.apply(nemo_sched[0]) dtrans.apply(nemo_sched[0]) # Disable node validation to avoid having to add a data region fort_writer = FortranWriter(check_global_constraints=False) result = fort_writer(nemo_sched) correct = '''!$acc begin parallel default(present) do i = 1, 20, 2 a = 2 * i + d(i) c(i) = a b(i) = b(i) + a + c(i) enddo !$acc end parallel''' assert correct in result cvisitor = CWriter(check_global_constraints=False) with pytest.raises(VisitorError) as err: _ = cvisitor(nemo_sched[0]) assert "Unsupported node 'ACCDataDirective' found" in str(err.value)
def trans(psy): '''PSyclone transformation script for the Dynamo0.3 API to optimise the matvec kernel for many-core CPUs. For the moment simply find the first matvec kernel in the example, transform the matmul intrinsic to equivalant inline code and then print out its PSyIR representation and output it as Fortran using the PSyIR Fortran back-end. ''' matmul2code_trans = Matmul2CodeTrans() fortran_writer = FortranWriter() for invoke in psy.invokes.invoke_list: schedule = invoke.schedule for kernel in schedule.coded_kernels(): if kernel.name.lower() == "matrix_vector_kernel_code": kernel_schedule = kernel.get_kernel_schedule() # Replace matmul with inline code for bin_op in kernel_schedule.walk(BinaryOperation): if bin_op.operator is BinaryOperation.Operator.MATMUL: matmul2code_trans.apply(bin_op) # Future optimisations will go here. kernel_schedule.view() result = fortran_writer(kernel_schedule) print(result) # Abort after the first matrix vector kernel for the # time being. print("Aborting to view the modifications to the matrix " "vector kernel") sys.exit() return psy
def test_apply_multi_assignments(): '''Check that the apply() method can be called for multiple assigments with and without ranges. ''' _, invoke_info = get_invoke("array_syntax.f90", api=API, idx=0) schedule = invoke_info.schedule trans = NemoAllArrayRange2LoopTrans() for assignment in schedule.walk(Assignment): trans.apply(assignment) writer = FortranWriter() result = writer(schedule) expected = ("do jk = 1, jpk, 1\n" " do jj = 1, jpj, 1\n" " do ji = 1, jpi, 1\n" " zftv(ji,jj,jk) = 0.0e0\n" " enddo\n" " enddo\n" "enddo\n" "if (l_ptr) then\n" " call dia_ptr_hst(jn, ''ldf'', -zftv(:,:,:))\n" "end if\n" "call dia_ptr_hst(jn, ''ldf'', -zftv(:,:,:))\n" "do jj = 1, jpj, 1\n" " do ji = 1, jpi, 1\n" " zftu(ji,jj,1) = 1.0e0\n" " enddo\n" "enddo\n" "do jj = 1, jpj, 1\n" " do ji = 1, jpi, 1\n" " tmask(ji,jj) = jpi\n" " enddo\n" "enddo\n") assert expected in result
def test_transform_apply_mixed_implicit_do(): '''Check that the PSyIR is transformed as expected for a lat,lon,levs loop with some of its indices accessed using array notation and some using explicit loops. The resultant Fortran code is used to confirm the transformation has worked correctly. ''' _, invoke_info = get_invoke("explicit_over_implicit.f90", api=API, idx=0) schedule = invoke_info.schedule assignment = schedule[0].loop_body[0] trans = NemoOuterArrayRange2LoopTrans() # Apply transformation 2 times, once for each implicit # dimension trans.apply(assignment) writer = FortranWriter() result = writer(schedule) expected = ( "do jk = 1, jpk, 1\n" " do jj = 1, jpj, 1\n" " umask(:,jj,jk) = vmask(:,jj,jk) + 1.0\n" " enddo\n" "enddo") assert expected in result trans.apply(assignment) result = writer(schedule) expected = ( "do jk = 1, jpk, 1\n" " do jj = 1, jpj, 1\n" " do ji = 1, jpi, 1\n" " umask(ji,jj,jk) = vmask(ji,jj,jk) + 1.0\n" " enddo\n" " enddo\n" "enddo") assert expected in result
def trans(psy): ''' Use the PSyIR back-end to generate PSy-layer target code''' invoke = psy.invokes.get('invoke_0_inc_field') schedule = invoke.schedule print("DSL level view:") schedule.view() print("f2pygen code:") print(str(psy.gen)) # In-place lowering to Language-level PSyIR schedule.lower_to_language_level() print("") print("Language level view:") schedule.view() fvisitor = FortranWriter() print("") print("FortranWriter code:") print(fvisitor(psy.container)) # This PSyclone call should terminate gracefully here sys.exit(0)
def generate_adjoint_str(tl_fortran_str): '''Takes an LFRic tangent-linear kernel encoded as a string as input and returns its adjoint encoded as a string. :param str tl_fortran_str: Fortran implementation of an LFRic \ tangent-linear kernel. :returns: a string containing the Fortran implementation of the \ supplied tangent-linear kernel. :rtype: str ''' logger = logging.getLogger(__name__) logger.debug(tl_fortran_str) # TL Language-level PSyIR reader = FortranReader() tl_psyir = reader.psyir_from_source(tl_fortran_str) # Addressing issue #1238 will allow the view() method to be output # to the logger. # logger.debug(tl_psyir.view()) # TL to AD translation ad_psyir = generate_adjoint(tl_psyir) # AD Fortran code writer = FortranWriter() adjoint_fortran_str = writer(ad_psyir) logger.debug(adjoint_fortran_str) return adjoint_fortran_str
def test_apply2(tmpdir): '''Test that the matmul2code apply method produces the expected PSyIR. We use the Fortran backend to help provide the test for correctness. This example includes extra indices for the vector and matrix arrays with additional indices being literals. ''' trans = Matmul2CodeTrans() matmul = create_matmul() matmul.children[0].children[2] = Literal("1", INTEGER_TYPE) matmul.children[1].children[1] = Literal("2", INTEGER_TYPE) trans.apply(matmul) writer = FortranWriter() result = writer(matmul.root) assert ("subroutine my_kern()\n" " integer, parameter :: idx = 3\n" " real, dimension(5,10,15) :: x\n" " real, dimension(10,20) :: y\n" " real, dimension(10) :: result\n" " integer :: i\n" " integer :: j\n" "\n" " do i = 1, 5, 1\n" " result(i)=0.0\n" " do j = 1, 10, 1\n" " result(i)=result(i) + x(i,j,1) * y(j,2)\n" " enddo\n" " enddo\n" "\n" "end subroutine my_kern" in result) assert Compile(tmpdir).string_compiles(result)
def test_transform_multi_apply(tmpdir): '''Check that the ArrayRange2Loop transformation can be used to create nested loops by calling it multiple times when an array has multiple dimensions that use a range. ''' trans = ArrayRange2LoopTrans() symbol_table = SymbolTable() symbol = DataSymbol("n", INTEGER_TYPE) symbol_table.add(symbol) lhs = create_array_y_2d_slice(symbol_table) rhs = create_array_z(symbol_table) assignment = Assignment.create(lhs, rhs) routine = KernelSchedule.create("work", symbol_table, [assignment]) trans.apply(assignment) trans.apply(assignment) expected = (" do idx = LBOUND(y2, 2), UBOUND(y2, 2), 1\n" " do idx_1 = LBOUND(y2, 1), UBOUND(y2, 1), 1\n" " y2(idx_1,idx)=z(idx_1,n,idx)\n" " enddo\n" " enddo\n") writer = FortranWriter() result = writer(routine) assert expected in result assert Compile(tmpdir).string_compiles(result)
def test_create(): '''Test that the static create() method creates a NemoLoop instance and its children corectly. ''' variable = DataSymbol("ji", INTEGER_TYPE) start = Literal("2", INTEGER_TYPE) stop = Literal("10", INTEGER_TYPE) step = Literal("1", INTEGER_TYPE) x_var = DataSymbol("X", REAL_TYPE) children = [Assignment.create(Reference(x_var), Literal("3.0", REAL_TYPE))] nemo_loop = NemoLoop.create(variable, start, stop, step, children) assert isinstance(nemo_loop, NemoLoop) assert nemo_loop.loop_type == "lon" assert isinstance(nemo_loop.loop_body, Schedule) assert len(nemo_loop.loop_body.children) == 1 assert nemo_loop.loop_body.children[0] is children[0] assert children[0].parent is nemo_loop.loop_body assert nemo_loop.loop_body.parent is nemo_loop assert nemo_loop.variable is variable assert nemo_loop.start_expr is start assert nemo_loop.stop_expr is stop assert nemo_loop.step_expr is step writer = FortranWriter() result = writer(nemo_loop) assert ("do ji = 2, 10, 1\n" " X = 3.0\n" "enddo" in result)
def test_transform_apply_insert(tmpdir): '''Check that the PSyIR is transformed as expected when there are multiple statements in the PSyIR. The resultant Fortran code is used to confirm the transformation has worked correctly. ''' trans = ArrayRange2LoopTrans() symbol_table = SymbolTable() symbol = DataSymbol("n", INTEGER_TYPE) symbol_table.add(symbol) # Create the first assignment. In Fortran notation: x(:) = y(n,:) lhs = create_array_x(symbol_table) rhs = create_array_y(symbol_table) assignment1 = Assignment.create(lhs, rhs) # Create the second assignment. In Fortran notation: y2(:,:) = z(:,n,:) lhs = create_array_y_2d_slice(symbol_table) rhs = create_array_z(symbol_table) assignment2 = Assignment.create(lhs, rhs) routine = KernelSchedule.create("work", symbol_table, [assignment1, assignment2]) trans.apply(assignment1) trans.apply(assignment2) writer = FortranWriter() expected = (" do idx = LBOUND(x, 1), UBOUND(x, 1), 1\n" " x(idx)=y(n,idx)\n" " enddo\n" " do idx_1 = LBOUND(y2, 2), UBOUND(y2, 2), 1\n" " y2(:,idx_1)=z(:,n,idx_1)\n" " enddo\n") result = writer(routine) assert expected in result assert Compile(tmpdir).string_compiles(result)
def test_apply_existing_names(): '''Check that the apply method uses existing iterators appropriately when their symbols are already defined. ''' _, invoke_info = get_invoke("implicit_do.f90", api=API, idx=0) schedule = invoke_info.schedule assignment = schedule[0] array_ref = assignment.lhs trans = NemoArrayRange2LoopTrans() symbol_table = schedule.symbol_table symbol_table.add(DataSymbol("ji", INTEGER_TYPE)) symbol_table.add(DataSymbol("jj", INTEGER_TYPE)) symbol_table.add(DataSymbol("jk", INTEGER_TYPE)) trans.apply(array_ref.children[2]) trans.apply(array_ref.children[1]) trans.apply(array_ref.children[0]) writer = FortranWriter() result = writer(schedule) assert ("do jk = 1, jpk, 1\n" " do jj = 1, jpj, 1\n" " do ji = 1, jpi, 1\n" " umask(ji,jj,jk) = 0.0e0\n" " enddo\n" " enddo\n" "enddo" in result)
def trans(psy): ''' Use the PSyIR back-end to generate PSy-layer target code''' # Loop over all of the Invokes in the PSy object for invoke in psy.invokes.invoke_list: print("Transforming invoke '" + invoke.name + "'...") schedule = invoke.schedule print("DSL level view:") schedule.view() print("f2pygen code:") print(str(psy.gen)) # TODO #1010: This script should terminate here until LFRic declares # all its symbols to the symbol table. sys.exit(0) for invoke in psy.invokes.invoke_list: # In-place lowering to Language-level PSyIR schedule.symbol_table.view() schedule.lower_to_language_level() print("") print("Language level view:") schedule.view() print("") print("FortranWriter code:") fvisitor = FortranWriter() print(fvisitor(psy.container))
def test_fw_exception(): '''Check the FortranWriter class instance raises an exception if an unsupported PSyIR node is found. ''' # Generate fparser2 parse tree from Fortran code. code = ( "module test\n" "contains\n" "subroutine tmp()\n" " integer :: a,b,c\n" " a = b/c\n" "end subroutine tmp\n" "end module test") schedule = create_schedule(code) # pylint: disable=abstract-method # modify the reference to b to be something unsupported class Unsupported(Node): '''A PSyIR node that will not be supported by the Fortran visitor.''' # pylint: enable=abstract-method unsupported = Unsupported() assignment = schedule.children[0] binary_operation = assignment.children[1] assignment.children[1] = unsupported unsupported.children = binary_operation.children # Generate Fortran from the PSyIR schedule fvisitor = FortranWriter() with pytest.raises(VisitorError) as excinfo: _ = fvisitor(schedule) assert "Unsupported node 'Unsupported' found" in str(excinfo)
def test_correct(func, output, tmpdir): '''Check that a valid example produces the expected output when the argument to ABS is a simple argument and when it is an expresssion. ''' Config.get().api = "nemo" operation = example_psyir(func) writer = FortranWriter() result = writer(operation.root) assert ("subroutine abs_example(arg)\n" " real, intent(inout) :: arg\n" " real :: psyir_tmp\n\n" " psyir_tmp = ABS({0})\n\n" "end subroutine abs_example\n".format(output)) in result trans = Abs2CodeTrans() trans.apply(operation, operation.root.symbol_table) result = writer(operation.root) assert ("subroutine abs_example(arg)\n" " real, intent(inout) :: arg\n" " real :: psyir_tmp\n" " real :: res_abs\n" " real :: tmp_abs\n\n" " tmp_abs = {0}\n" " if (tmp_abs > 0.0) then\n" " res_abs = tmp_abs\n" " else\n" " res_abs = tmp_abs * -1.0\n" " end if\n" " psyir_tmp = res_abs\n\n" "end subroutine abs_example\n".format(output)) in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def test_acc_data_region_no_struct(parser): ''' Test that we refuse to generate code if a data region includes references to structures. ''' reader = FortranStringReader(''' module test use some_mod, only: grid_type type(grid_type) :: grid contains subroutine tmp() integer :: i integer, dimension(:) :: b do i = 1, 20, 2 b(i) = b(i) + i + grid%flag grid%data(i) = i enddo end subroutine tmp end module test''') code = parser(reader) psy = PSyFactory("nemo", distributed_memory=False).create(code) sched = psy.invokes.invoke_list[0].schedule dtrans = ACCDataTrans() dtrans.apply(sched) fvisitor = FortranWriter() with pytest.raises(NotImplementedError) as err: _ = fvisitor(sched) # Allow for the vagaries of py2 output versus py3 err_msg = str(err.value).replace("u'", "'") assert ("Structure (derived-type) references are not yet supported within " "OpenACC data regions but found: ['grid%flag', 'grid%data(i)']" in err_msg)
def gen_code(self, parent): ''' Generate the Fortran Loop and any associated code. :param parent: the node in the f2pygen AST to which to add content. :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` ''' # Avoid circular dependency # pylint: disable=import-outside-toplevel from psyclone.psyGen import zero_reduction_variables, InvokeSchedule def is_unit_literal(expr): ''' Check if the given expression is equal to the literal '1'. :param expr: a PSyIR expression. :type expr: :py:class:`psyclone.psyir.nodes.Node` :returns: True if it is equal to the literal '1', false otherwise. ''' return isinstance(expr, Literal) and expr.value == '1' if not self.is_openmp_parallel(): calls = self.reductions() zero_reduction_variables(calls, parent) invoke = self.ancestor(InvokeSchedule) if invoke.opencl or (is_unit_literal(self.start_expr) and is_unit_literal(self.stop_expr)): # no need for a loop for child in self.loop_body: child.gen_code(parent) else: # Avoid circular dependency # pylint: disable=import-outside-toplevel from psyclone.psyir.backend.fortran import FortranWriter # start/stop/step_expr are generated with the FortranWriter # backend, the rest of the loop with f2pygen. fwriter = FortranWriter() if is_unit_literal(self.step_expr): step_str = None else: step_str = fwriter(self.step_expr) do_stmt = DoGen(parent, self.variable.name, fwriter(self.start_expr), fwriter(self.stop_expr), step_str) # need to add do loop before children as children may want to add # info outside of do loop parent.add(do_stmt) for child in self.loop_body: child.gen_code(do_stmt) my_decl = DeclGen(parent, datatype="integer", entity_decls=[self.variable.name]) parent.add(my_decl)