def test_compiler_works(tmpdir): ''' Check that the specified compiler works for a hello-world example ''' Compile.skip_if_compilation_disabled() old_pwd = tmpdir.chdir() try: with open("hello_world.f90", "w") as ffile: ffile.write(HELLO_CODE) Compile(tmpdir).compile_file("hello_world.f90", link=True) finally: old_pwd.chdir()
def test_find_fortran_file(tmpdir): ''' Check that our find_fortran_file routine raises the expected error if it can't find a matching file. Also check that it returns the correct name if the file does exist. ''' with pytest.raises(IOError) as excinfo: Compile.find_fortran_file([str(tmpdir)], "missing_file") assert "missing_file' with suffix in ['f90', 'F90'," in str(excinfo) old_pwd = tmpdir.chdir() try: with open("hello_world.f90", "w") as ffile: ffile.write(HELLO_CODE) name = Compile.find_fortran_file([str(tmpdir)], "hello_world") assert name.endswith("hello_world.f90") finally: old_pwd.chdir()
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_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_replicated_loop(parser, tmpdir): '''Check code generation with two loops that have the same structure. ''' reader = FortranStringReader("subroutine replicate()\n" " INTEGER :: dummy\n" " REAL :: zwx(10,10)\n" " zwx(:,:) = 0.e0\n" " zwx(:,:) = 0.e0\n" "END subroutine replicate\n") code = parser(reader) psy = PSyFactory(API, distributed_memory=False).create(code) schedule = psy.invokes.get('replicate').schedule acc_trans = TransInfo().get_trans_name('ACCDataTrans') schedule, _ = acc_trans.apply(schedule.children[0:1]) schedule, _ = acc_trans.apply(schedule.children[1:2]) gen_code = str(psy.gen) assert (" !$ACC DATA COPYOUT(zwx)\n" " zwx(:, :) = 0.E0\n" " !$ACC END DATA\n" " !$ACC DATA COPYOUT(zwx)\n" " zwx(:, :) = 0.E0\n" " !$ACC END DATA" in gen_code) assert Compile(tmpdir).string_compiles(gen_code)
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_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_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_correct_2abs(tmpdir): '''Check that a valid example produces the expected output when there is more than one ABS() in an expression. ''' Config.get().api = "nemo" operation = example_psyir( lambda arg: BinaryOperation.create( BinaryOperation.Operator.MUL, arg, Literal("3.14", REAL_TYPE))) root = operation.root assignment = operation.parent abs_op = UnaryOperation.create(UnaryOperation.Operator.ABS, Literal("1.0", REAL_TYPE)) operation.detach() op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, operation, abs_op) assignment.addchild(op1) writer = FortranWriter() result = writer(root) assert ( "subroutine abs_example(arg)\n" " real, intent(inout) :: arg\n" " real :: psyir_tmp\n\n" " psyir_tmp = ABS(arg * 3.14) + ABS(1.0)\n\n" "end subroutine abs_example\n") in result trans = Abs2CodeTrans() trans.apply(operation, root.symbol_table) trans.apply(abs_op, root.symbol_table) result = writer(root) assert ( "subroutine abs_example(arg)\n" " real, intent(inout) :: arg\n" " real :: psyir_tmp\n" " real :: res_abs\n" " real :: tmp_abs\n" " real :: res_abs_1\n" " real :: tmp_abs_1\n\n" " tmp_abs = arg * 3.14\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" " tmp_abs_1 = 1.0\n" " if (tmp_abs_1 > 0.0) then\n" " res_abs_1 = tmp_abs_1\n" " else\n" " res_abs_1 = tmp_abs_1 * -1.0\n" " end if\n" " psyir_tmp = res_abs + res_abs_1\n\n" "end subroutine abs_example\n") in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def infra_compile(tmpdir_factory, request): '''A per-session initialisation function that sets the compilation flags in the Compile class based on command line options for --compile, --compileopencl, --f90, --f90flags. Then makes sure that the infrastructure files for the dynamo0p3 and gocean1p0 APIs are compiled (if compilation was enabled). ''' Compile.store_compilation_flags(request.config) # Create a temporary directory to store the compiled files. # Note that this directory is unique even if compiled in # parallel, i.e. each process has its own copy of the # compiled infrastructure file, which avoids the problem # of synchronisation between the processes. tmpdir = tmpdir_factory.mktemp('dynamo_wrapper') # This is the first instance created. This will trigger # compilation of the infrastructure files. LFRicBuild(tmpdir) tmpdir = tmpdir_factory.mktemp('dl_esm_inf') GOcean1p0Build(tmpdir)
def test_correct_2min(tmpdir): '''Check that a valid example produces the expected output when there is more than one MIN() in an expression. ''' Config.get().api = "nemo" operation = example_psyir_binary(lambda arg: arg) root = operation.root assignment = operation.parent operation.detach() min_op = BinaryOperation.create(BinaryOperation.Operator.MIN, Literal("1.0", REAL_TYPE), Literal("2.0", REAL_TYPE)) op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, min_op, operation) assignment.addchild(op1) writer = FortranWriter() result = writer(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(1.0, 2.0) + MIN(arg, arg_1)\n\n" "end subroutine min_example\n") in result trans = Min2CodeTrans() trans.apply(operation, root.symbol_table) trans.apply(min_op, root.symbol_table) result = writer(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" " real :: res_min_1\n" " real :: tmp_min_1\n\n" " res_min = arg\n" " tmp_min = arg_1\n" " if (tmp_min < res_min) then\n" " res_min = tmp_min\n" " end if\n" " res_min_1 = 1.0\n" " tmp_min_1 = 2.0\n" " if (tmp_min_1 < res_min_1) then\n" " res_min_1 = tmp_min_1\n" " end if\n" " psyir_tmp = res_min_1 + res_min\n\n" "end subroutine min_example\n") in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def test_compiler_with_flags(tmpdir): ''' Check that we can pass through flags to the Fortran compiler. Since correct flags are compiler-dependent and hard to test, we pass something that is definitely not a flag and check that the compiler complains. This test is skipped if no compilation tests have been requested (--compile flag to py.test). ''' Compile.skip_if_compilation_disabled() old_pwd = tmpdir.chdir() try: with open("hello_world.f90", "w") as ffile: ffile.write(HELLO_CODE) _compile = Compile(tmpdir) _compile._f90flags = "not-a-flag" with pytest.raises(CompileError) as excinfo: _compile.compile_file("hello_world.f90") assert "not-a-flag" in str(excinfo.value) # For completeness we also try with a valid flag although we # can't actually check its effect. _compile._f90flags = "-g" _compile.compile_file("hello_world.f90", link=True) finally: old_pwd.chdir()
def test_opencl_compiler_works(kernel_outputdir): ''' Check that the specified compiler works for a hello-world opencl example. This is done in this file to alert the user that all compiles tests are skipped if only the '--compile' command line option is used (instead of --compileopencl) ''' Compile.skip_if_opencl_compilation_disabled() example_ocl_code = ''' program hello USE fortcl write (*,*) "Hello" end program hello ''' old_pwd = kernel_outputdir.chdir() try: with open("hello_world_opencl.f90", "w") as ffile: ffile.write(example_ocl_code) GOcean1p0OpenCLBuild(kernel_outputdir).\ compile_file("hello_world_opencl.f90", link=True) finally: old_pwd.chdir()
def test_correct_expr(tmpdir): '''Check that a valid example produces the expected output when SIGN is part of an expression. ''' Config.get().api = "nemo" operation = example_psyir(lambda arg: BinaryOperation.create( BinaryOperation.Operator.MUL, arg, Literal("3.14", REAL_TYPE))) root = operation.root assignment = operation.parent operation.detach() op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, Literal("1.0", REAL_TYPE), operation) op2 = BinaryOperation.create(BinaryOperation.Operator.ADD, op1, Literal("2.0", REAL_TYPE)) assignment.addchild(op2) writer = FortranWriter() result = writer(root) assert ("subroutine sign_example(arg, arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n\n" " psyir_tmp = 1.0 + SIGN(arg * 3.14, arg_1) + 2.0\n\n" "end subroutine sign_example\n") in result trans = Sign2CodeTrans() trans.apply(operation, root.symbol_table) result = writer(root) assert ("subroutine sign_example(arg, arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n" " real :: res_sign\n" " real :: tmp_sign\n" " real :: res_abs\n" " real :: tmp_abs\n\n" " tmp_abs = arg * 3.14\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" " res_sign = res_abs\n" " tmp_sign = arg_1\n" " if (tmp_sign < 0.0) then\n" " res_sign = res_sign * -1.0\n" " end if\n" " psyir_tmp = 1.0 + res_sign + 2.0\n\n" "end subroutine sign_example\n") in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def test_compile_str(monkeypatch, tmpdir): ''' Checks for the routine that compiles Fortran supplied as a string ''' # Check that we always return True if compilation testing is disabled Compile.skip_if_compilation_disabled() _compile = Compile(tmpdir) test_compile = "psyclone.tests.utilities.Compile" monkeypatch.setattr(test_compile + ".TEST_COMPILE", False) monkeypatch.setattr(test_compile + ".TEST_COMPILE_OPENCL", False) assert _compile.string_compiles("not fortran") # Re-enable compilation testing and check that we can build hello world monkeypatch.setattr(test_compile + ".TEST_COMPILE", True) assert _compile.string_compiles(HELLO_CODE) # Repeat for some broken code invalid_code = HELLO_CODE.replace("write", "wite", 1) assert not _compile.string_compiles(invalid_code)
def test_apply3(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 the array and vector being passed with no index information. ''' trans = Matmul2CodeTrans() matmul = create_matmul() root = matmul.root matrix = matmul.children[0] lhs_vector = matrix.parent.parent.lhs matrix_symbol = matrix.symbol matmul.children[0] = Reference(matrix_symbol) matrix_symbol.datatype._shape = [ Literal("10", INTEGER_TYPE), Literal("20", INTEGER_TYPE) ] rhs_vector = matmul.children[1] rhs_vector_symbol = rhs_vector.symbol rhs_vector_symbol.datatype._shape = [Literal("20", INTEGER_TYPE)] matmul.children[1] = Reference(rhs_vector_symbol) lhs_vector_symbol = lhs_vector.symbol lhs_vector_symbol._shape = [Literal("10", INTEGER_TYPE)] lhs_vector.replace_with(Reference(lhs_vector_symbol)) trans.apply(matmul) writer = FortranWriter() result = writer(root) assert ("subroutine my_kern()\n" " integer, parameter :: idx = 3\n" " real, dimension(10,20) :: x\n" " real, dimension(20) :: y\n" " real, dimension(10) :: result\n" " integer :: i\n" " integer :: j\n" "\n" " do i = 1, 10, 1\n" " result(i) = 0.0\n" " do j = 1, 20, 1\n" " result(i) = result(i) + x(i,j) * y(j)\n" " enddo\n" " enddo\n" "\n" "end subroutine my_kern" in result) assert Compile(tmpdir).string_compiles(result)
def test_transform_apply(lhs_create, rhs_create, expected, tmpdir): '''Check that the PSyIR is transformed as expected for various types of ranges in an array. 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) lhs = lhs_create(symbol_table) rhs = rhs_create(symbol_table) assignment = Assignment.create(lhs, rhs) routine = KernelSchedule.create("work", symbol_table, [assignment]) trans.apply(assignment) writer = FortranWriter() result = writer(routine) assert expected in result assert Compile(tmpdir).string_compiles(result)
def test_correct_expr(tmpdir): '''Check that a valid example produces the expected output when MIN() is part of an expression. ''' Config.get().api = "nemo" operation = example_psyir_binary(lambda arg: arg) assignment = operation.parent op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, Literal("1.0", REAL_TYPE), operation) op2 = BinaryOperation.create(BinaryOperation.Operator.ADD, op1, Literal("2.0", REAL_TYPE)) op2.parent = assignment assignment.children[1] = op2 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=1.0 + MIN(arg, arg_1) + 2.0\n\n" "end subroutine min_example\n") 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=arg\n" " tmp_min=arg_1\n" " if (tmp_min < res_min) then\n" " res_min=tmp_min\n" " end if\n" " psyir_tmp=1.0 + res_min + 2.0\n\n" "end subroutine min_example\n") in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def test_correct_nary(tmpdir): '''Check that a valid example with an nary MIN produces the expected output. ''' Config.get().api = "nemo" operation = example_psyir_nary() root = operation.root writer = FortranWriter() result = writer(root) assert ("subroutine min_example(arg, arg_1, arg_2)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real, intent(inout) :: arg_2\n" " real :: psyir_tmp\n\n" " psyir_tmp = MIN(arg, arg_1, arg_2)\n\n" "end subroutine min_example\n") in result trans = Min2CodeTrans() trans.apply(operation, operation.root.symbol_table) result = writer(root) assert ("subroutine min_example(arg, arg_1, arg_2)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real, intent(inout) :: arg_2\n" " real :: psyir_tmp\n" " real :: res_min\n" " real :: tmp_min\n\n" " res_min = arg\n" " tmp_min = arg_1\n" " if (tmp_min < res_min) then\n" " res_min = tmp_min\n" " end if\n" " tmp_min = arg_2\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") in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def test_build_invalid_fortran(tmpdir): ''' Check that we raise the expected error when attempting to compile some invalid Fortran. Skips test if --compile not supplied to py.test on command-line. ''' Compile.skip_if_compilation_disabled() invalid_code = HELLO_CODE.replace("write", "wite", 1) old_pwd = tmpdir.chdir() try: with open("hello_world.f90", "w") as ffile: ffile.write(invalid_code) _compile = Compile(tmpdir) with pytest.raises(CompileError) as excinfo: _compile.compile_file("hello_world.f90") finally: old_pwd.chdir() assert "Compile error" in str(excinfo.value)
def test_fuse_correct_bounds(tmpdir, fortran_reader, fortran_writer): ''' Test that loop boundaries must be identical. ''' # TODO: This test needs evaluation # of constant expressions in PSyclone code = '''subroutine sub() integer :: ji, jj, n integer, dimension(10,10) :: s, t do jj=2, n do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo do jj=2, n+1-1 do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo end subroutine sub''' out, _ = fuse_loops(code, fortran_reader, fortran_writer) assert Compile(tmpdir).string_compiles(out)
def test_apply4(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 make the lhs be the same array as the second operand of the matmul (the vector in this case). ''' trans = Matmul2CodeTrans() one = Literal("1", INTEGER_TYPE) five = Literal("5", INTEGER_TYPE) matmul = create_matmul() root = matmul.root assignment = matmul.parent vector = assignment.scope.symbol_table.lookup("y") assignment.children[0] = ArrayReference.create( vector, [Range.create(one, five, one.copy()), one.copy()]) trans.apply(matmul) writer = FortranWriter() result = writer(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" " y(i,1) = 0.0\n" " do j = 1, 10, 1\n" " y(i,1) = y(i,1) + x(i,j,idx) * y(j,idx)\n" " enddo\n" " enddo\n" "\n" "end subroutine my_kern" in result) assert Compile(tmpdir).string_compiles(result)
def test_correct_2sign(tmpdir): '''Check that a valid example produces the expected output when there is more than one SIGN in an expression. ''' Config.get().api = "nemo" operation = example_psyir(lambda arg: BinaryOperation.create( BinaryOperation.Operator.MUL, arg, Literal("3.14", REAL_TYPE))) assignment = operation.parent sign_op = BinaryOperation.create(BinaryOperation.Operator.SIGN, Literal("1.0", REAL_TYPE), Literal("1.0", REAL_TYPE)) op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, sign_op, operation) op1.parent = assignment assignment.children[1] = op1 writer = FortranWriter() result = writer(operation.root) assert ("subroutine sign_example(arg,arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n\n" " psyir_tmp=SIGN(1.0, 1.0) + SIGN(arg * 3.14, arg_1)\n\n" "end subroutine sign_example\n") in result trans = Sign2CodeTrans() trans.apply(operation, operation.root.symbol_table) trans.apply(sign_op, operation.root.symbol_table) result = writer(operation.root) assert ("subroutine sign_example(arg,arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n" " real :: res_sign\n" " real :: tmp_sign\n" " real :: res_abs\n" " real :: tmp_abs\n" " real :: res_sign_1\n" " real :: tmp_sign_1\n" " real :: res_abs_1\n" " real :: tmp_abs_1\n\n" " tmp_abs=arg * 3.14\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" " res_sign=res_abs\n" " tmp_sign=arg_1\n" " if (tmp_sign < 0.0) then\n" " res_sign=res_sign * -1.0\n" " end if\n" " tmp_abs_1=1.0\n" " if (tmp_abs_1 > 0.0) then\n" " res_abs_1=tmp_abs_1\n" " else\n" " res_abs_1=tmp_abs_1 * -1.0\n" " end if\n" " res_sign_1=res_abs_1\n" " tmp_sign_1=1.0\n" " if (tmp_sign_1 < 0.0) then\n" " res_sign_1=res_sign_1 * -1.0\n" " end if\n" " psyir_tmp=res_sign_1 + res_sign\n\n" "end subroutine sign_example\n") in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def test_fuse_scalars(tmpdir, fortran_reader, fortran_writer): '''Test that using scalars work as expected in all combinations of being read/written in both loops. ''' # First test: read/read of scalar variable code = '''subroutine sub() integer :: ji, jj, n real, dimension(10,10) :: s, t real :: a do jj=1, n do ji=1, 10 s(ji, jj) = t(ji, jj) + a enddo enddo do jj=1, n do ji=1, 10 t(ji, jj) = t(ji, jj) - a enddo enddo end subroutine sub''' out, _ = fuse_loops(code, fortran_reader, fortran_writer) assert Compile(tmpdir).string_compiles(out) # Second test: read/write of scalar variable code = '''subroutine sub() integer :: ji, jj, n real, dimension(10,10) :: s, t real :: a do jj=1, n do ji=1, 10 s(ji, jj)=t(ji, jj)+a enddo enddo do jj=1, n do ji=1, 10 a = t(ji, jj) - 2 s(ji, jj)=t(ji, jj)+a enddo enddo end subroutine sub''' with pytest.raises(TransformationError) as err: fuse_loops(code, fortran_reader, fortran_writer) assert "Scalar variable 'a' is written in one loop, but only read in " \ "the other loop." in str(err.value) # Third test: write/read of scalar variable code = '''subroutine sub() integer :: ji, jj, n real, dimension(10,10) :: s, t real :: b do jj=1, n do ji=1, 10 b = t(ji, jj) - 2 s(ji, jj )=t(ji, jj)+b enddo enddo do jj=1, n do ji=1, 10 s(ji, jj)=t(ji, jj)+b enddo enddo end subroutine sub''' with pytest.raises(TransformationError) as err: fuse_loops(code, fortran_reader, fortran_writer) assert "Scalar variable 'b' is written in one loop, but only read in " \ "the other loop." in str(err.value) # Fourth test: write/write of scalar variable - this is ok code = '''subroutine sub() integer :: ji, jj, n real, dimension(10,10) :: s, t real :: b do jj=1, n do ji=1, 10 b = t(ji, jj) - 2 s(ji, jj )=t(ji, jj)+b enddo enddo do jj=1, n do ji=1, 10 b = sqrt(t(ji, jj)) s(ji, jj)=t(ji, jj)+b enddo enddo end subroutine sub''' out, _ = fuse_loops(code, fortran_reader, fortran_writer) assert Compile(tmpdir).string_compiles(out)
def test_fuse_dimension_change(tmpdir, fortran_reader, fortran_writer): '''Test that inconsistent use of dimemsions are detected, e.g.: loop1: a(i,j) loop2: a(j,i) when at least one operation is a write ''' # The first example can be merged, since 't' is read-only, # so it doesn't matter that it is accessed differently code = '''subroutine sub() integer :: ji, jj, n integer, dimension(10,10) :: s, t do jj=1, n+1 do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo do jj=1, n+1 do ji=1, 10 s(ji, jj)=s(ji, jj) + t(jj, jj) + t(ji, ji) enddo enddo end subroutine sub''' out, _ = fuse_loops(code, fortran_reader, fortran_writer) correct = """ do jj = 1, n + 1, 1 do ji = 1, 10, 1 s(ji,jj) = t(ji,jj) + 1 enddo do ji = 1, 10, 1 s(ji,jj) = s(ji,jj) + t(jj,jj) + t(ji,ji) enddo enddo""" assert correct in out assert Compile(tmpdir).string_compiles(out) # This cannot be fused, since 's' is written in the # first iteration and read in the second. code = '''subroutine sub() integer :: ji, jj, n integer, dimension(10,10) :: s, t, u do jj=1, n+1 do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo do jj=1, n+1 do ji=1, 10 u(ji, jj)=s(jj, ji)+1 enddo enddo end subroutine sub''' with pytest.raises(TransformationError) as err: fuse_loops(code, fortran_reader, fortran_writer) assert "Variable 's' is written to in one or both of the loops and the "\ "loop variable jj is used in different index locations (1 and 0) "\ "when accessing it." in str(err.value) # This cannot be fused, since 's' is read in the # first iteration and written in the second with # different indices. code = '''subroutine sub() integer :: ji, jj, n integer, dimension(10,10) :: s, t, u do jj=1, n+1 do ji=1, 10 u(ji, jj)=s(jj, ji)+1 enddo enddo do jj=1, n+1 do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo end subroutine sub''' with pytest.raises(TransformationError) as err: fuse_loops(code, fortran_reader, fortran_writer) assert "Variable 's' is written to in one or both of the loops and the " \ "loop variable jj is used in different index locations (0 and 1) " \ "when accessing it." in str(err.value)
def test_fuse_incorrect_bounds_step(tmpdir, fortran_reader, fortran_writer): ''' Test that loop boundaries and step size must be identical. ''' # Lower loop boundary code = '''subroutine sub() integer :: ji, jj, n integer, dimension(10,10) :: s, t do jj=1, n do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo do jj=2, n do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo end subroutine sub''' with pytest.raises(TransformationError) as err: fuse_loops(code, fortran_reader, fortran_writer) assert "Lower loop bounds must be identical, but are" in str(err.value) # Upper loop boundary code = '''subroutine sub() integer :: ji, jj, n integer, dimension(10,10) :: s, t do jj=1, n do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo do jj=1, n+1 do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo end subroutine sub''' with pytest.raises(TransformationError) as err: fuse_loops(code, fortran_reader, fortran_writer) assert "Upper loop bounds must be identical, but are" in str(err.value) # Test step size: code = '''subroutine sub() integer :: ji, jj, n integer, dimension(10,10) :: s, t do jj=1, n, 2 do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo do jj=1, n do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo end subroutine sub''' with pytest.raises(TransformationError) as err: fuse_loops(code, fortran_reader, fortran_writer) assert "Step size in loops must be identical, but are" in str(err.value) # Test step size - make sure it defaults to 1 code = '''subroutine sub() integer :: ji, jj, n integer, dimension(10,10) :: s, t do jj=1, n, 1 do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo do jj=1, n do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo end subroutine sub''' out, _ = fuse_loops(code, fortran_reader, fortran_writer) assert Compile(tmpdir).string_compiles(out)
def test_fuse_ok(tmpdir, fortran_reader, fortran_writer): '''This tests verifies that loop fusion can be successfully applied to conformant loops. ''' code = '''subroutine sub() integer :: ji, jj, n integer, dimension(10,10) :: s, t do jj=1, n do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo do jj=1, n do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo end subroutine sub''' out, psyir = fuse_loops(code, fortran_reader, fortran_writer) expected = """ do jj = 1, n, 1 do ji = 1, 10, 1 s(ji,jj) = t(ji,jj) + 1 enddo do ji = 1, 10, 1 s(ji,jj) = t(ji,jj) + 1 enddo enddo""" assert expected in out assert Compile(tmpdir).string_compiles(out) # Then fuse the inner ji loops fuse = NemoLoopFuseTrans() fuse.apply(psyir[0].loop_body[0], psyir[0].loop_body[1]) out = fortran_writer(psyir) expected = """ do jj = 1, n, 1 do ji = 1, 10, 1 s(ji,jj) = t(ji,jj) + 1 s(ji,jj) = t(ji,jj) + 1 enddo enddo""" assert expected in out assert Compile(tmpdir).string_compiles(out) # Test more complex loop boundaries. Note that # we might actually consider simplifying these # expressions code = '''subroutine sub() integer :: ji, jj, n integer, dimension(10,10) :: s, t do jj=2-1, n+1-1 do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo do jj=2-1, n+1-1 do ji=1, 10 s(ji, jj)=t(ji, jj)+1 enddo enddo end subroutine sub''' out, _ = fuse_loops(code, fortran_reader, fortran_writer) expected = """ do jj = 2 - 1, n + 1 - 1, 1 do ji = 1, 10, 1 s(ji,jj) = t(ji,jj) + 1 enddo do ji = 1, 10, 1 s(ji,jj) = t(ji,jj) + 1 enddo enddo""" assert expected in out assert Compile(tmpdir).string_compiles(out)