diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4279288..2e71a3c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -78,6 +78,11 @@ jobs: container: snowstep/llvm:jammy # https://hub.docker.com/r/phhargrove/llvm-flang/tags + - os: ubuntu-24.04-arm + compiler: flang + version: 23 + container: phhargrove/llvm-flang:23.1.0-latest-arm64 + - os: ubuntu-24.04 compiler: flang version: 23 @@ -143,13 +148,8 @@ jobs: # https://hub.docker.com/r/phhargrove/lfortran/tags - os: ubuntu-24.04 compiler: lfortran - version: 0.54.0 - container: phhargrove/lfortran:0.54.0-1 - -# - os: ubuntu-24.04 -# compiler: lfortran -# version: 0.55.0 -# container: phhargrove/lfortran:0.55.0-1 + version: 0.55.0 + container: phhargrove/lfortran:0.55.0-1 # # - os: ubuntu-24.04 # compiler: lfortran @@ -186,11 +186,6 @@ jobs: version: 0.62.0 container: phhargrove/lfortran:0.62.0-1 - - os: ubuntu-24.04 - compiler: lfortran - version: 0.63.0 - container: phhargrove/lfortran:0.63.0-1 - # https://github.com/lfortran/lfortran/pkgs/container/lfortran - os: ubuntu-22.04 compiler: lfortran @@ -207,6 +202,11 @@ jobs: version: 0.65.0 container: ghcr.io/lfortran/lfortran:v0.65.0 + - os: ubuntu-22.04 + compiler: lfortran + version: 0.66.0 + container: ghcr.io/lfortran/lfortran:v0.66.0 + - os: ubuntu-22.04 compiler: lfortran version: latest @@ -328,10 +328,11 @@ jobs: if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} run: | export FPM_VERSION=0.13.0 + mkdir -p fpm-temp/bin + cd fpm-temp curl --retry 5 -LOsS https://github.com/fortran-lang/fpm/releases/download/v$FPM_VERSION/fpm-$FPM_VERSION.F90 - mkdir fpm-temp - gfortran-14 -o fpm-temp/fpm fpm-$FPM_VERSION.F90 - echo "PATH=${PWD}/fpm-temp:${PATH}" >> "$GITHUB_ENV" + gfortran-14 -o bin/fpm fpm-$FPM_VERSION.F90 + echo "PATH=$PWD/bin:${PATH}" >> "$GITHUB_ENV" - name: Version info run: | @@ -362,11 +363,21 @@ jobs: ( set +e ; eval fpm run --example invoke-via-macro ${FPM_FLAGS} --flag \"$FFLAGS\" $CHECK_ASSERT ) - name: Test Assertions w/ Parallel Callbacks - if: ${{ matrix.compiler != 'lfortran' || matrix.version != '0.54.0' }} # issue #68, fixed in 0.55 env: - FPM_FLAGS: ${{ env.FPM_FLAGS }} --flag -DASSERT_MULTI_IMAGE --flag -DASSERT_PARALLEL_CALLBACKS + FPM_FLAGS: ${{ env.FPM_FLAGS }} --flag -DTEST_PARALLEL_CALLBACKS + run: | + fpm run --example false-assertion ${FPM_FLAGS} --flag "$FFLAGS" + fpm run --example invoke-via-macro ${FPM_FLAGS} --flag "$FFLAGS" + ( set +e ; eval fpm run --example false-assertion ${FPM_FLAGS} --flag \"$FFLAGS\" $CHECK_ASSERT ) + ( set +e ; eval fpm run --example invoke-via-macro ${FPM_FLAGS} --flag \"$FFLAGS\" $CHECK_ASSERT ) + + - name: Test Assertions w/ ASSERT_MULTI_IMAGE + if: matrix.compiler == 'gfortran' + env: + FPM_FLAGS: ${{ env.FPM_FLAGS }} --flag -fcoarray=single --flag -DASSERT_MULTI_IMAGE run: | fpm run --example false-assertion ${FPM_FLAGS} --flag "$FFLAGS" fpm run --example invoke-via-macro ${FPM_FLAGS} --flag "$FFLAGS" ( set +e ; eval fpm run --example false-assertion ${FPM_FLAGS} --flag \"$FFLAGS\" $CHECK_ASSERT ) ( set +e ; eval fpm run --example invoke-via-macro ${FPM_FLAGS} --flag \"$FFLAGS\" $CHECK_ASSERT ) + diff --git a/README.md b/README.md index 79ed46d..dcb9dcc 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ preprocessor ASSERTIONS to non-zero, e.g., ``` fpm build --flag "-DASSERTIONS" ``` -The program [example/invoke-via-macro.F90] demonstrates the preferred way to invoke assertions via the three provided macros. +The program [example/invoke-via-macro.F90] demonstrates the preferred way to invoke assertions via the provided macros. Invoking assertions this way ensures such calls will be completely removed whenever the `ASSERTIONS` macro is undefined (or defined to zero) during compilation. Due to a limitation of `fpm`, this approach works best if the project using Assert is also a `fpm` project. If instead `fpm install` is used, then either the user must copy `include/assert_macros.h` to the installation directory (default: `~/.local/include`) or @@ -84,7 +84,7 @@ Building and Testing - [GNU Compiler Collection (GCC) `gfortran`](#gnu-compiler-collection-gcc-gfortran)) - [Intel `ifx`](#intel-ifx)) - [LFortran `lfortran`](#lfortran-lfortran) -- [LLVM `flang-new`](#llvm-flang-new) +- [LLVM `flang`](#llvm-flang) - [Numerical Algorithms Group (NAG) `nagfor`](#numerical-algorithms-group-nag-nagfor) ### General Build Knobs @@ -98,16 +98,19 @@ using syntax like: `fpm --flag "-DASSERTIONS=1"` * `ASSERT_MULTI_IMAGE`: Controls whether the library attempts to use multi-image Fortran features (e.g. to report the image number of an assertion failure). - The default is compiler-specific. Multi-image support can be disabled using - `-DASSERT_MULTI_IMAGE=0`. + Default is disabled, multi-image support can be enabled using `-DASSERT_MULTI_IMAGE`. -* `ASSERT_PARALLEL_CALLBACKS`: Controls the use of a callback interface for - multi-process features. Contact us for more details. +### Cray Compiler Environment (CCE) `crayftn` -### Cray Compiler Environment (CCE) `ftn` -Because `fpm` uses the compiler name to determine the compiler identity and because -CCE provides one compiler wrapper, `ftn`, for invoking all compilers, you will -need to invoke `ftn` in a shell script named to identify CCE compiler. For example, +The simplest way to compile with CCE is to invoke the Cray compiler +directly: +``` +fpm test --compiler crayftn --profile release +``` + +If instead you prefer to use the Cray PE compiler wrappers, note that `fpm` uses +the compiler name to determine the compiler identity, so you will +need to invoke `ftn` in a shell script named to identify the CCE compiler. For example, place a script named `crayftn.sh` in your path with the following contents and with executable privileges set appropriately: ``` @@ -136,11 +139,11 @@ The above commands build the Assert library (with the default of assertion enfor #### Multi-image (parallel) execution With `gfortran` 14 or later versions and OpenCoarrays installed, use ``` -fpm test --compiler caf --profile release --runner "cafrun -n 2" +fpm test --compiler caf --profile release --runner "cafrun -n 2" --flag -DASSERT_MULTI_IMAGE ``` With `gfortran` 13 or earlier versions and OpenCoarrays installed, ``` -fpm test --compiler caf --profile release --runner "cafrun -n 2" --flag "-ffree-line-length-0" +fpm test --compiler caf --profile release --runner "cafrun -n 2" --flag "-DASSERT_MULTI_IMAGE -ffree-line-length-0" ``` ### Intel `ifx` @@ -156,16 +159,16 @@ With Intel Fortran and Intel MPI installed, fpm test --compiler ifx --profile release --flag "-coarray -DASSERT_MULTI_IMAGE" ``` -### LLVM `flang-new` +### LLVM `flang` #### Single-image (serial) execution -With `flang-new` version 19, use +With LLVM Flang version 19, use ``` fpm test --compiler flang-new --flag "-mmlir -allow-assumed-rank -O3" ``` -With `flang-new` version 20 or later, use +With LLVM Flang version 20 or later, use ``` -fpm test --compiler flang-new --flag "-O3" +fpm test --compiler flang --flag "-O3" ``` ### LFortran `lfortran` @@ -180,17 +183,13 @@ fpm test --compiler lfortran --profile release --flag --cpp #### Single-image (serial) execution With `nagfor` version 7.1 or later, use ``` -fpm test --compiler nagfor --flag -fpp +fpm test --compiler nagfor -DASSERTIONS ``` #### Multi-image execution -With `nagfor` 7.1, use -``` -fpm test --compiler nagfor --profile release --flag "-fpp -coarray=cosmp -f2018" -``` -With `nagfor` 7.2 or later, use +With `nagfor` 7.1 or later, use ``` -fpm test --compiler nagfor --flag -fpp +fpm test --compiler nagfor --flag -DASSERT_MULTI_IMAGE ``` Documentation @@ -242,15 +241,15 @@ In the case of gfortran, this appears to have been resolved by default starting #### Line breaks in macro invocations The preprocessor is not currently specified by any Fortran standard, and -as of 2025 its operation differs in subtle ways between compilers. +as of 2026 its operation differs in subtle ways between compilers. One way in which compilers differ is how macro invocations can safely be broken across multiple lines. -For example, gfortran and flang-new both accept backslash `\` continuation +For example, GNU `gfortran` and LLVM `flang` both accept backslash `\` continuation character for line-breaks in a macro invocation: ```fortran -! OK for flang-new and gfortran +! OK for flang and gfortran call_assert_describe( computed_checksum == expected_checksum, \ "Checksum mismatch failure!" \ ) @@ -276,7 +275,7 @@ Fortran does not support comments with an end delimiter, only to-end-of-line comments. As such, there is no portable way to safely insert a Fortran comment into the middle of a macro invocation. For example, the following seemingly reasonable code results in a syntax error -after macro expansion (on gfortran and flang-new): +after macro expansion (on gfortran and flang): ```fortran ! INCORRECT: cannot use Fortran comments inside macro invocation diff --git a/example/false-assertion.F90 b/example/false-assertion.F90 index f8e8e86..1da3c0f 100644 --- a/example/false-assertion.F90 +++ b/example/false-assertion.F90 @@ -2,22 +2,21 @@ program false_assertion use assert_m implicit none -#if ASSERT_PARALLEL_CALLBACKS +#if TEST_PARALLEL_CALLBACKS assert_this_image => assert_callback_this_image assert_error_stop => assert_callback_error_stop #endif call assert(.false., "false-assertion: unconditionally failing test") -#if ASSERT_PARALLEL_CALLBACKS +#if TEST_PARALLEL_CALLBACKS ! By default, assert uses `THIS_IMAGE()` in multi-image mode while ! composing assertion output, and invokes `ERROR STOP` to print the ! assertion and terminate execution. ! -! The ASSERT_PARALLEL_CALLBACKS preprocessor flag enables the client to replace +! The parallel callbacks features enables the client to replace ! the default use of these two Fortran features with client-provided callbacks. -! To use this feature, the client must build the library with `-DASSERT_PARALLEL_CALLBACKS`, -! and then at startup set the `assert_this_image` and `assert_error_stop` +! To use this feature, the client must ! set the `assert_this_image` and `assert_error_stop` ! procedure pointers to reference the desired callbacks. contains diff --git a/include/assert_features.h b/include/assert_features.h index f5bd4e5..05a8538 100644 --- a/include/assert_features.h +++ b/include/assert_features.h @@ -2,18 +2,9 @@ #define _ASSERT_FEATURES_H ! Whether or not the assert library may use multi-image features -! Default is compiler-dependent +! Disabled by default since many compilers require extra arguments to enable multi-image features #ifndef ASSERT_MULTI_IMAGE -# if defined(__flang__) || defined(__INTEL_COMPILER) || defined(__LFORTRAN__) -# define ASSERT_MULTI_IMAGE 0 -# else -# define ASSERT_MULTI_IMAGE 1 -# endif -#endif - -! Whether the library should use client callbacks for parallel features -#ifndef ASSERT_PARALLEL_CALLBACKS -#define ASSERT_PARALLEL_CALLBACKS 0 +#define ASSERT_MULTI_IMAGE 0 #endif #endif diff --git a/src/assert_m.F90 b/src/assert_m.F90 index 05f2097..cdeac0d 100644 --- a/src/assert_m.F90 +++ b/src/assert_m.F90 @@ -37,27 +37,25 @@ module assert_m private public :: assert, assert_always -#if ASSERT_PARALLEL_CALLBACKS - public :: assert_this_image_interface, assert_this_image - public :: assert_error_stop_interface, assert_error_stop - - abstract interface - pure function assert_this_image_interface() result(this_image_id) - implicit none - integer :: this_image_id - end function - end interface - procedure(assert_this_image_interface), pointer :: assert_this_image - - abstract interface - pure subroutine assert_error_stop_interface(stop_code_char) - implicit none - character(len=*), intent(in) :: stop_code_char - end subroutine - end interface - procedure(assert_error_stop_interface), pointer :: assert_error_stop + ! Parallel callbacks support + public :: assert_this_image_interface, assert_this_image + public :: assert_error_stop_interface, assert_error_stop -#endif + abstract interface + pure function assert_this_image_interface() result(this_image_id) + implicit none + integer :: this_image_id + end function + end interface + procedure(assert_this_image_interface), pointer :: assert_this_image => null() + + abstract interface + pure subroutine assert_error_stop_interface(stop_code_char) + implicit none + character(len=*), intent(in) :: stop_code_char + end subroutine + end interface + procedure(assert_error_stop_interface), pointer :: assert_error_stop => null() #ifndef USE_ASSERTIONS # if ASSERTIONS @@ -71,14 +69,14 @@ pure subroutine assert_error_stop_interface(stop_code_char) contains - pure subroutine assert(assertion, description) - !! If assertion is .false. and enforcement is enabled (e.g. via -DASSERTIONS=1), - !! then error-terminate with a character stop code that contains the description argument if present - implicit none - logical, intent(in) :: assertion - !! Most assertions will be expressions such as i>0 - character(len=*), intent(in) :: description - !! A brief statement of what is being asserted such as "i>0" or "positive i" + pure subroutine assert(assertion, description) + !! If assertion is .false. and enforcement is enabled (e.g. via -DASSERTIONS=1), + !! then error-terminate with a character stop code that contains the description argument if present + implicit none + logical, intent(in) :: assertion + !! Most assertions will be expressions such as i>0 + character(len=*), intent(in) :: description + !! A brief statement of what is being asserted such as "i>0" or "positive i" toggle_assertions: & if (enforce_assertions) then @@ -87,78 +85,72 @@ pure subroutine assert(assertion, description) end subroutine - pure subroutine assert_always(assertion, description, file, line) - !! Same as above but always enforces the assertion (regardless of ASSERTIONS) - implicit none - logical, intent(in) :: assertion - character(len=*), intent(in) :: description - character(len=*), intent(in), optional :: file - integer, intent(in), optional :: line + pure subroutine assert_always(assertion, description, file, line) + !! Same as above but always enforces the assertion (regardless of ASSERTIONS) + implicit none + logical, intent(in) :: assertion + character(len=*), intent(in) :: description + character(len=*), intent(in), optional :: file + integer, intent(in), optional :: line + character(len=:), allocatable :: message character(len=:), allocatable :: location integer me - check_assertion: & - if (.not. assertion) then - ! Avoid harmless warnings from Cray Fortran: - allocate(character(len=0)::message) - allocate(character(len=0)::location) - - - ! format source location, if known - location = '' - if (present(file)) then - location = ' at ' // file // ':' - if (present(line)) then ! only print line number if file is also known - block - character(len=128) line_str - write(line_str, '(i0)') line - location = location // trim(adjustl(line_str)) - end block - else - location = location // '' - end if - end if - -#if ASSERT_MULTI_IMAGE -# if ASSERT_PARALLEL_CALLBACKS - if (associated(assert_this_image)) then - me = assert_this_image() + check_assertion: & + if (.not. assertion) then + ! Avoid harmless warnings from Cray Fortran: + allocate(character(len=0)::message) + allocate(character(len=0)::location) + + ! format source location, if known + location = '' + if (present(file)) then + location = ' at ' // file // ':' + if (present(line)) then ! only print line number if file is also known + block + character(len=128) line_str + write(line_str, '(i0)') line + location = location // trim(adjustl(line_str)) + end block else - me = 0 + location = location // '' end if -# else - me = this_image() -# endif - block - character(len=128) image_number - write(image_number, *) me - message = 'Assertion failure on image ' // trim(adjustl(image_number)) // location // ': ' // description - end block -#else + end if + +# if ASSERT_MULTI_IMAGE + me = this_image() +# else + me = 0 +# endif + if (associated(assert_this_image)) then + me = assert_this_image() + end if + if (me > 0) then + block + character(len=128) image_number + write(image_number, *) me + message = 'Assertion failure on image ' // trim(adjustl(image_number)) & + // location // ': ' // description + end block + else message = 'Assertion failure' // location // ': ' // description - me = 0 ! avoid a harmless warning -#endif - -#if ASSERT_PARALLEL_CALLBACKS - if (associated(assert_this_image)) then - call assert_error_stop(message) - else - ; ! deliberate fall-thru - end if -#endif + end if + + if (associated(assert_error_stop)) then + call assert_error_stop(message) + end if #ifdef __LFORTRAN__ - ! workaround a defect observed in LFortran 0.54: - ! error stop with an allocatable character argument prints garbage - error stop message//'', QUIET=.false. + ! workaround a defect observed in LFortran 0.54: + ! error stop with an allocatable character argument prints garbage + error stop message//'', QUIET=.false. #elif __GNUC__ && __GNUC__ < 12 - ! old GFortran lacks the QUIET optional arg added in F2018 - error stop message + ! old GFortran lacks the QUIET optional arg added in F2018 + error stop message #else - error stop message, QUIET=.false. + error stop message, QUIET=.false. #endif - - end if check_assertion + end if check_assertion end subroutine