diff --git a/Fortran/UnitTests/CMakeLists.txt b/Fortran/UnitTests/CMakeLists.txt index 07269cbac..84979dd70 100644 --- a/Fortran/UnitTests/CMakeLists.txt +++ b/Fortran/UnitTests/CMakeLists.txt @@ -1,3 +1,4 @@ # This file should only contain add_subdirectory(...) one for each test add_subdirectory(hello) add_subdirectory(fcvs21_f95) # NIST Fortran Compiler Validation Suite +add_subdirectory(finalization) diff --git a/Fortran/UnitTests/finalization/CMakeLists.txt b/Fortran/UnitTests/finalization/CMakeLists.txt new file mode 100644 index 000000000..51d870e85 --- /dev/null +++ b/Fortran/UnitTests/finalization/CMakeLists.txt @@ -0,0 +1 @@ +add_executable(test_finalization compile_me_only.f90) diff --git a/Fortran/UnitTests/finalization/README.md b/Fortran/UnitTests/finalization/README.md new file mode 100644 index 000000000..2009c27cb --- /dev/null +++ b/Fortran/UnitTests/finalization/README.md @@ -0,0 +1,163 @@ +Type Finalization Tests +======================= + +Standalone compilation and execution steps and results are below for the +following compilers: + +* [NAG] +* [GNU] +* [Cray] +* [Intel] +* [NVIDIA] +* [IBM] +* [AMD] +* [LLVM] + +NAG +--- +- Version: 7.1 (Build 7113) +- Result: 0 test failures. +``` +nagfor compile_me_only.f90 +./a.out + Pass: finalizes a non-allocatable object on the LHS of an intrinsic assignment + Pass: finalizes an allocated allocatable LHS of an intrinsic assignment + Pass: finalizes a target when the associated pointer is deallocated + Pass: finalizes an object upon explicit deallocation + Pass: finalizes a non-pointer non-allocatable object at the END statement + Pass: finalizes a non-pointer non-allocatable object at the end of a block construct + Pass: finalizes a function reference on the RHS of an intrinsic assignment + Pass: finalizes a specification expression function result + Pass: finalizes an intent(out) derived type dummy argument + Pass: finalizes an allocatable component object +``` + +GNU +--- +- Version: 12.2.0 +- Result: 4 test failures. +``` +gfortran compile_me_only.f90 +./a.out + Fail: finalizes a non-allocatable object on the LHS of an intrinsic assignment + Fail: finalizes an allocated allocatable LHS of an intrinsic assignment + Pass: finalizes a target when the associated pointer is deallocated + Pass: finalizes an object upon explicit deallocation + Pass: finalizes a non-pointer non-allocatable object at the END statement + Pass: finalizes a non-pointer non-allocatable object at the end of a block construct + Fail: finalizes a function reference on the RHS of an intrinsic assignment + Fail: finalizes a specification expression function result + Pass: finalizes an intent(out) derived type dummy argument + Pass: finalizes an allocatable component object +``` + +Cray +---- +- Version: 13.0.1 +- Result: 3 test failures. +``` +ftn compile_me_only.f90 +./a.out +Cray Fortran : Version 13.0.1 fails to compile specification_expression_finalization.f90 +Pass: finalizes a non-allocatable object on the LHS of an intrinsic assignment +Fail: finalizes an allocated allocatable LHS of an intrinsic assignment +Pass: finalizes a target when the associated pointer is deallocated +Pass: finalizes an object upon explicit deallocation +Pass: finalizes a non-pointer non-allocatable object at the END statement +Pass: finalizes a non-pointer non-allocatable object at the end of a block construct +Pass: finalizes a function reference on the RHS of an intrinsic assignment +Fail: finalizes a specification expression function result +Fail: finalizes an intent(out) derived type dummy argument +Pass: finalizes an allocatable component object +``` + +Intel +----- +- Version: 2021.1 Beta Build 20200827 +- Result: 2 test failures. +``` +ifort compile_me_only.f90 +./a.out +Pass: finalizes a non-allocatable object on the LHS of an intrinsic assignment +Pass: finalizes an allocated allocatable LHS of an intrinsic assignment +Pass: finalizes a target when the associated pointer is deallocated +Pass: finalizes an object upon explicit deallocation +Pass: finalizes a non-pointer non-allocatable object at the END statement +Pass: finalizes a non-pointer non-allocatable object at the end of a block construct +Fail: finalizes a function reference on the RHS of an intrinsic assignment +Pass: finalizes a specification expression function result +Pass: finalizes an intent(out) derived type dummy argument +Fail: finalizes an allocatable component object + +``` + +NVIDIA +------ +- Version: 22.7-0 64-bit target on x86-64 Linux -tp zen3 +- Result: 2 test failures + +``` +nvfortran compile_me_only.f90 +./a.out +Pass: finalizes a non-allocatable object on the LHS of an intrinsic assignment +Fail: finalizes an allocated allocatable LHS of an intrinsic assignment +Pass: finalizes a target when the associated pointer is deallocated +Pass: finalizes an object upon explicit deallocation +Pass: finalizes a non-pointer non-allocatable object at the END statement +Pass: finalizes a non-pointer non-allocatable object at the end of a block construct +Pass: finalizes a function reference on the RHS of an intrinsic assignment +Pass: finalizes a specification expression function result +Fail: finalizes an intent(out) derived type dummy argument +Pass: finalizes an allocatable component object +``` + +IBM +--- +- Version: IBM Open XL Fortran for AIX 17.1.0 +- Result: 1 test failure + +In order to for the tests to complete in a way that reports all of the results, +place an exclamation mark (`!`) at the beginning of the following line in the +`compile_me_only.f90` file: +``` +,test_result_t("finalizes a specification expression function result", specification_expression()) & +``` +which removes the one failing test. Compiling and excuting the same file then + +``` +xlf2003_r compile_me_only.f90 +./a.out + Pass: finalizes a non-allocatable object on the LHS of an intrinsic assignment + Pass: finalizes an allocated allocatable LHS of an intrinsic assignment + Pass: finalizes a target when the associated pointer is deallocated + Pass: finalizes an object upon explicit deallocation + Pass: finalizes a non-pointer non-allocatable object at the END statement + Pass: finalizes a non-pointer non-allocatable object at the end of a block construct + Pass: finalizes a function reference on the RHS of an intrinsic assignment + Pass: finalizes an intent(out) derived type dummy argument + Pass: finalizes an allocatable component object +``` +**Fail:** Separately compiling `specification_expression_finalization.f90` with +`xlf2003_r` causes a core dump. This is a compiler bug that has been reported to +via the Oak Ridge Leadership Computing Facility (OLCF) under ticket OLCFHELP-9069. + +AMD +--- +- Version tested: 13.0.0 (AOCC_3.2.0-Build#128 2021_11_12) +- Result: Fails to build due to an internal compiler error (ICE) + + +LLVM +---- +- Version tested: `git` commit `76911b5f75907eef53a30cc3` +- Result: Fails with the error message `not yet implemented: derived type finalization` + +[NAG]: #nag +[GNU]: #gnu +[Cray]: #cray +[Intel]: #intel +[NVIDIA]: #nvidia +[IBM]: #ibm +[AMD]: #amd +[LLVM]: #llvm + diff --git a/Fortran/UnitTests/finalization/compile_me_only.f90 b/Fortran/UnitTests/finalization/compile_me_only.f90 new file mode 100644 index 000000000..ece3014bf --- /dev/null +++ b/Fortran/UnitTests/finalization/compile_me_only.f90 @@ -0,0 +1,293 @@ +module test_result_m + !! Define tests for each scenario in which the Fortran 2018 + !! standard mandates type finalization. + implicit none + + private + public :: test_result_t, get_test_results + + type test_result_t + character(len=132) description + logical outcome + end type + + type object_t + integer dummy + contains + final :: count_finalizations + end type + + type wrapper_t + private + type(object_t), allocatable :: object + end type + + integer :: finalizations = 0 + integer, parameter :: avoid_unused_variable_warning = 1 + +contains + + function get_test_results() result(test_results) + type(test_result_t), allocatable :: test_results(:) + + test_results = [ & + test_result_t("finalizes a non-allocatable object on the LHS of an intrinsic assignment", lhs_object()) & + ,test_result_t("finalizes an allocated allocatable LHS of an intrinsic assignment", allocated_allocatable_lhs()) & + ,test_result_t("finalizes a target when the associated pointer is deallocated", target_deallocation()) & + ,test_result_t("finalizes an object upon explicit deallocation", finalize_on_deallocate()) & + ,test_result_t("finalizes a non-pointer non-allocatable object at the END statement", finalize_on_end()) & + ,test_result_t("finalizes a non-pointer non-allocatable object at the end of a block construct", block_end()) & + ,test_result_t("finalizes a function reference on the RHS of an intrinsic assignment", rhs_function_reference()) & + ,test_result_t("finalizes a specification expression function result", specification_expression()) & + ,test_result_t("finalizes an intent(out) derived type dummy argument", intent_out()) & + ,test_result_t("finalizes an allocatable component object", allocatable_component()) & + ] + end function + + function construct_object() result(object) + !! Constructor for object_t + type(object_t) object + object % dummy = avoid_unused_variable_warning + end function + + subroutine count_finalizations(self) + !! Destructor for object_t + type(object_t), intent(inout) :: self + finalizations = finalizations + 1 + self % dummy = avoid_unused_variable_warning + end subroutine + + function lhs_object() result(outcome) + !! Test conformance with Fortran 2018 clause 7.5.6.3, paragraph 1 behavior: + !! "not an unallocated allocatable variable" + type(object_t) lhs, rhs + logical outcome + integer initial_tally + + rhs%dummy = avoid_unused_variable_warning + initial_tally = finalizations + lhs = rhs ! finalizes lhs + associate(finalization_tally => finalizations - initial_tally) + outcome = finalization_tally==1 + end associate + end function + + function allocated_allocatable_lhs() result(outcome) + !! Test conformance with Fortran 2018 clause 7.5.6.3, paragraph 1 behavior: + !! "allocated allocatable variable" + type(object_t), allocatable :: lhs + type(object_t) rhs + logical outcome + integer initial_tally + + rhs%dummy = avoid_unused_variable_warning + initial_tally = finalizations + allocate(lhs) + lhs = rhs ! finalizes lhs + associate(finalization_tally => finalizations - initial_tally) + outcome = finalization_tally==1 + end associate + end function + + function target_deallocation() result(outcome) + !! Test conformance with Fortran 2018 clause 7.5.6.3, paragraph 2 behavior: + !! "pointer is deallocated" + type(object_t), pointer :: object_ptr => null() + logical outcome + integer initial_tally + + allocate(object_ptr, source=object_t(dummy=0)) + initial_tally = finalizations + deallocate(object_ptr) ! finalizes object + associate(finalization_tally => finalizations - initial_tally) + outcome = finalization_tally==1 + end associate + end function + + function allocatable_component() result(outcome) + !! Test conformance with Fortran 2018 clause 7.5.6.3, para. 2 ("allocatable entity is deallocated") + !! + 9.7.3.2, para. 6 ("INTENT(OUT) allocatable dummy argument is deallocated") + type(wrapper_t), allocatable :: wrapper + logical outcome + integer initial_tally + + initial_tally = finalizations + + allocate(wrapper) + allocate(wrapper%object) + call finalize_intent_out_component(wrapper) + associate(finalization_tally => finalizations - initial_tally) + outcome = finalization_tally==1 + end associate + + contains + + subroutine finalize_intent_out_component(output) + type(wrapper_t), intent(out) :: output ! finalizes object component + allocate(output%object) + output%object%dummy = avoid_unused_variable_warning + end subroutine + + end function + + function finalize_on_deallocate() result(outcome) + !! Test conformance with Fortran 2018 clause 7.5.6.3, paragraph 2: + !! "allocatable entity is deallocated" + type(object_t), allocatable :: object + logical outcome + integer initial_tally + + initial_tally = finalizations + allocate(object) + object%dummy = 1 + deallocate(object) ! finalizes object + associate(final_tally => finalizations - initial_tally) + outcome = final_tally==1 + end associate + end function + + function finalize_on_end() result(outcome) + !! Test conformance with Fortran 2018 clause 7.5.6.3, paragraph 3: + !! "before return or END statement" + logical outcome + integer initial_tally + + initial_tally = finalizations + call finalize_on_end_subroutine() ! Finalizes local_obj + associate(final_tally => finalizations - initial_tally) + outcome = final_tally==1 + end associate + + contains + + subroutine finalize_on_end_subroutine() + type(object_t) local_obj + local_obj % dummy = avoid_unused_variable_warning + end subroutine + + end function + + function block_end() result(outcome) + !! Test conformance with Fortran 2018 clause 7.5.6.3, paragraph 4: + !! "termination of the BLOCK construct" + logical outcome + integer initial_tally + + initial_tally = finalizations + block + type(object_t) object + object % dummy = avoid_unused_variable_warning + end block ! Finalizes object + associate(finalization_tally => finalizations - initial_tally) + outcome = finalization_tally==1 + end associate + end function + + function rhs_function_reference() result(outcome) + !! Test conformance with Fortran 2018 clause 7.5.6.3, paragraph 5 behavior: + !! "nonpointer function result" + type(object_t), allocatable :: object + logical outcome + integer initial_tally + + initial_tally = finalizations + object = construct_object() ! finalizes object_t result + associate(finalization_tally => finalizations - initial_tally) + outcome = finalization_tally==1 + end associate + end function + + function specification_expression() result(outcome) + !! Test conformance with Fortran 2018 standard clause 7.5.6.3, paragraph 6: + !! "specification expression function result" + use iso_fortran_env, only : compiler_version + logical outcome + integer compile_status, execution_status + character(len=:), allocatable :: compile + + compile = compile_command() // & + " -o specification_expression_finalization specification_expression_finalization.f90 > /dev/null 2>&1" + call execute_command_line( & + command = compile, & + wait = .true., & + exitstat = compile_status & + ) + if (compile_status/=0) print *, compiler_version() // " fails to compile specification_expression_finalization.f90" + + call execute_command_line( & + command = "./specification_expression_finalization > /dev/null 2>&1", & + wait = .true., & + exitstat = execution_status & + ) + associate(program_compiled => compile_status==0, error_termination_occurred => execution_status/=0) + outcome = program_compiled .and. error_termination_occurred + end associate + + contains + + function compile_command() result(command) + character(len=:), allocatable :: command + + associate(compiler_identity=>compiler_version()) + if (scan(compiler_identity, "nvfortran") == 1) then + command = "nvfortran" + else if (scan(compiler_identity, "Intel") == 1) then + command = "ifort" + else if (scan(compiler_identity, "Cray") == 1) then + command = "ftn" + else if (scan(compiler_identity, "GCC") == 1) then + command = "gfortran" + else if (scan(compiler_identity, "NAG") == 1) then + command = "nagfor" + else + command = "" + end if + end associate + end function + + end function + + function intent_out() result(outcome) + !! Test conformance with Fortran 2018 standard clause 7.5.6.3, paragraph 7: + !! "nonpointer, nonallocatable, INTENT (OUT) dummy argument" + logical outcome + type(object_t) object + integer initial_tally + + initial_tally = finalizations + call finalize_intent_out_arg(object) + associate(finalization_tally => finalizations - initial_tally) + outcome = finalization_tally==1 + end associate + contains + subroutine finalize_intent_out_arg(output) + type(object_t), intent(out) :: output ! finalizes output + output%dummy = avoid_unused_variable_warning + end subroutine + end function + +end module test_result_m + +program main + !! Test each scenario in which the Fortran 2018 standard + !! requires type finalization. + use test_result_m, only : test_result_t, get_test_results + implicit none + type(test_result_t), allocatable :: test_results(:) + integer i + + test_results = get_test_results() + + do i=1,size(test_results) + print *, report(test_results(i)%outcome), test_results(i)%description + end do + +contains + + pure function report(outcome) + logical, intent(in) :: outcome + character(len=:), allocatable :: report + report = merge("Pass: ", "Fail: ", outcome) + end function + +end program diff --git a/Fortran/UnitTests/finalization/specification_expression_finalization.f90 b/Fortran/UnitTests/finalization/specification_expression_finalization.f90 new file mode 100644 index 000000000..7ee8719cd --- /dev/null +++ b/Fortran/UnitTests/finalization/specification_expression_finalization.f90 @@ -0,0 +1,67 @@ +module finalizable_m + !! This module supports the main program at the bottom of this file, which + !! tests compiler conformance with clause 7.5.6.3, paragraph 6 in the Fortran + !! Interpretation Document (https://j3-fortran.org/doc/year/18/18-007r1.pdf): + !! "If a specification expression in a scoping unit references + !! a function, the result is finalized before execution of the executable + !! constructs in the scoping unit." (The same statement appears in clause + !! 4.5.5.2, paragraph 5 of the Fortran 2003 standard.) In such a scenario, + !! the final subroutine must be pure. The only way to observe output from + !! a pure final subroutine is for the subroutine to execute an error stop + !! statement. A correct execution of this test will error-terminate and ouput + !! the text "finalize: intentional error termination to verify finalization". + implicit none + + private + public :: finalizable_t, component + + type finalizable_t + private + integer, pointer :: component_ => null() + contains + final :: finalize + end Type + + interface finalizable_t + module procedure construct + end interface + +contains + + pure function construct(component) result(finalizable) + integer, intent(in) :: component + type(finalizable_t) finalizable + allocate(finalizable%component_, source = component) + end function + + pure function component(self) result(self_component) + type(finalizable_t), intent(in) :: self + integer self_component + if (.not. associated(self%component_)) error stop "component: unassociated component" + self_component = self%component_ + end function + + pure subroutine finalize(self) + type(finalizable_t), intent(inout) :: self + if (associated(self%component_)) deallocate(self%component_) + error stop "finalize: intentional error termination to verify finalization" + end subroutine + +end module + +program specification_expression_finalization + !! Test the finalization of a function result in a specification expression + use finalizable_m, only : finalizable_t, component + implicit none + + call finalize_specification_expression_result + +contains + + subroutine finalize_specification_expression_result + real tmp(component(finalizable_t(component=0))) !! Finalizes the finalizable_t function result + real eliminate_unused_variable_warning + tmp = eliminate_unused_variable_warning + end subroutine + +end program