From 862ba930a17ad4d8080df6a91e1bb4edacccb471 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Wed, 7 Aug 2024 22:11:08 -0500 Subject: [PATCH 1/7] doc(README): describe invocation via macro (cherry picked from commit d3e23f030b98f0076fc42b4f1ae7d21aa8f56eeb) --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 55f7721..a23788f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ Assert ====== -A simple assertion utility taking advantage of the Fortran 2018 standard's introduction of variable stop codes -and error termination inside pure procedures. +An assertion utility that combines variable stop codes and error termination in `pure` procedures to produce descriptive messages when a program detects violations of the requirements for correct execution. Motivations ----------- @@ -11,18 +10,26 @@ Motivations Overview -------- -This assertion utility contains three public entities: +This assertion utility contains four public entities: 1. An `assert` subroutine, 2. A `characterizable_t` abstract type supporting `assert`, and 3. An `intrinsic_array_t` non-abstract type extending `characterizable_t`. +4. A `assert_macros.h` header file containing C-preprocessor macros. The `assert` subroutine - * Error-terminates with a variable stop code when a user-defined logical assertion fails, * Includes user-supplied diagnostic data in the output if provided by the calling procedure, * Is callable inside `pure` procedures, and -* Can be eliminated during an optimizing compiler's dead-code removal phase based on a preprocessor macro: `-DUSE_ASSERTIONS=.false.`. +* Can be eliminated at compile-time. + +The program [example/invoke-via-macro.F90] demonstrates the preferred way to invoke the `assert` subroutine via the three provided macros. +Invoking `assert` this way insures that `assert` invocations will be completely removed whenever the `DEBUG` macro is set 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 +the user must invoke `assert` directly (via `call assert(...)`). +If invoked directly, the user can set pass `-DUSE_ASSERTIONS=.false.` at compile time. +The latter approach cause `assert` to start and end with `if (.false.) then ... end if`, which might facilitate automatic removal of `assert` during the dead-code removal phase of optimizing compilers. The `characterizable_t` type defines an `as_character()` deferred binding that produces `character` strings for use as diagnostic output from a user-defined derived type that extends `characterizable_t` and implements the deferred binding. @@ -237,3 +244,4 @@ See the [LICENSE](LICENSE) file for copyright and licensing information. [OCL]: https://en.wikipedia.org/wiki/Object_Constraint_Language [Assert's GitHub Pages site]: https://berkeleylab.github.io/assert/ [`ford`]: https://github.com/Fortran-FOSS-Programmers/ford +[example/invoke-via-macro.F90]: ./example/invoke-via-macro.F90 From 55b484f2b0aa3134ffb5eaafe2b238706c26dc02 Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Wed, 2 Oct 2024 11:54:41 -0700 Subject: [PATCH 2/7] Change macro control knob for call_assert* to ASSERTIONS=1/0 The DEBUG=def/undef macro control knob for the call_assert* macros is renamed to ASSERTIONS, and changed to a non-zero/zero value convention. When undefined, ASSERTIONS is given the default value of 0 (disabling the call_assert macros). Update test and example to match the new convention. --- example/invoke-via-macro.F90 | 16 ++++++++++++---- include/assert_macros.h | 7 ++++++- test/test-assert-macro.F90 | 21 ++++++++++++--------- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/example/invoke-via-macro.F90 b/example/invoke-via-macro.F90 index 3799990..b89df91 100644 --- a/example/invoke-via-macro.F90 +++ b/example/invoke-via-macro.F90 @@ -2,24 +2,32 @@ program invoke_via_macro !! Demonstrate how to invoke the 'assert' subroutine using a preprocessor macro that facilitates - !! the complete removal of the call in the absence of the compiler flag -DDEBUG. + !! the complete removal of the call in the absence of the compiler flag: -DASSERTIONS use assert_m, only : assert, intrinsic_array_t, string !! If an "only" clause is employed as above, it must include the "string" function that the !! call_assert* macros reference when transforming the code below into "assert" subroutine calls. implicit none -#ifndef DEBUG +#if !ASSERTIONS print * - print *,'To enable the "assert" call, define -DDEBUG, e.g., fpm run --example invoke-via-macro --flag "-DDEBUG -fcoarray=single"' + print *,'To enable the "call_assert" invocations, define the ASSERTIONS macro. e.g.:' + print *,' fpm run --example invoke-via-macro --flag "-DASSERTIONS -fcoarray=single -ffree-line-length-0"' print * #endif ! The C preprocessor will convert each call_assert* macro below into calls to the "assert" subroutine - ! (if -DDEBUG is in the compiler command) or into nothing (if -DDEBUG is not in the compiler command). + ! whenever the ASSERTIONS macro is defined to non-zero (e.g. via the -DASSERTIONS compiler flag). + ! Whenever the ASSERTIONS macro is undefined or defined to zero (e.g. via the -DASSERTIONS=0 compiler flag), + ! these calls will be entirely removed by the preprocessor. call_assert(1==1) ! true assertion call_assert_describe(2>0, "example assertion invocation via macro") ! true assertion call_assert_diagnose(1+1==2, "example with scalar diagnostic data", 1+1) ! true assertion +#if ASSERTIONS + print * + print *,'Here comes the expected assertion failure:' + print * +#endif call_assert_diagnose(1+1>2, "example with array diagnostic data" , intrinsic_array_t([1,1,2])) ! false assertion end program invoke_via_macro diff --git a/include/assert_macros.h b/include/assert_macros.h index 065f8d1..c1b4872 100644 --- a/include/assert_macros.h +++ b/include/assert_macros.h @@ -6,7 +6,12 @@ #undef call_assert_describe #undef call_assert_diagnose -#ifdef DEBUG +#ifndef ASSERTIONS +! Assertions are off by default +#define ASSERTIONS 0 +#endif + +#if ASSERTIONS # define call_assert(assertion) call assert(assertion, "No description provided (see file " // __FILE__ // ", line " // string(__LINE__) // ")") # define call_assert_describe(assertion, description) call assert(assertion, description // " in file " // __FILE__ // ", line " // string(__LINE__) // ": " ) # define call_assert_diagnose(assertion, description, diagnostic_data) call assert(assertion, "file " // __FILE__ // ", line " // string(__LINE__) // ": " // description, diagnostic_data) diff --git a/test/test-assert-macro.F90 b/test/test-assert-macro.F90 index 770df48..23fc603 100644 --- a/test/test-assert-macro.F90 +++ b/test/test-assert-macro.F90 @@ -5,35 +5,38 @@ program test_assert_macros print * print *,"The call_assert macro" -#define DEBUG +#undef ASSERTIONS +#define ASSERTIONS 1 #include "assert_macros.h" call_assert(1==1) print *," passes on not error-terminating when an assertion expression evaluating to .true. is the only argument" -#undef DEBUG +#undef ASSERTIONS #include "assert_macros.h" call_assert(.false.) - print *," passes on being removed by the preprocessor when DEBUG is undefined" // new_line('') + print *," passes on being removed by the preprocessor when ASSERTIONS is undefined" // new_line('') !------------------------------------------ print *,"The call_assert_describe macro" -#define DEBUG +#undef ASSERTIONS +#define ASSERTIONS 1 #include "assert_macros.h" call_assert_describe(.true., ".true.") print *," passes on not error-terminating when assertion = .true. and a description is present" -#undef DEBUG +#undef ASSERTIONS #include "assert_macros.h" call_assert_describe(.false., "") - print *," passes on being removed by the preprocessor when DEBUG is undefined" // new_line('') + print *," passes on being removed by the preprocessor when ASSERTIONS is undefined" // new_line('') !------------------------------------------ print *,"The call_assert_diagnose macro" -#define DEBUG +#undef ASSERTIONS +#define ASSERTIONS 1 #include "assert_macros.h" call_assert_diagnose(.true., ".true.", diagnostic_data=1) print *," passes on not error-terminating when assertion = .true. and description and diagnostic_data are present" @@ -53,9 +56,9 @@ program test_assert_macros end block -#undef DEBUG +#undef ASSERTIONS #include "assert_macros.h" call_assert_describe(.false., "") - print *," passes on being removed by the preprocessor when DEBUG is undefined" + print *," passes on being removed by the preprocessor when ASSERTIONS is undefined" end program From 012fc82a5db061ae682ba952a63474e82360985e Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Wed, 2 Oct 2024 19:34:46 -0700 Subject: [PATCH 3/7] Change preprocessor control knob in assert_subroutine_m to ASSERTIONS=1/0 --- src/assert/assert_subroutine_m.F90 | 31 ++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/assert/assert_subroutine_m.F90 b/src/assert/assert_subroutine_m.F90 index 90e0e11..7ac135c 100644 --- a/src/assert/assert_subroutine_m.F90 +++ b/src/assert/assert_subroutine_m.F90 @@ -1,33 +1,48 @@ +! (c) 2024 UC Regents, see LICENSE file for detailed terms. ! ! (c) 2019-2020 Guide Star Engineering, LLC ! This Software was developed for the US Nuclear Regulatory Commission (US NRC) under contract ! "Multi-Dimensional Physics Implementation into Fuel Analysis under Steady-state and Transients (FAST)", ! contract # NRC-HQ-60-17-C-0007 ! +#include "assert_macros.h" + module assert_subroutine_m - !! summary: Utility for runtime checking of logical assertions. + !! summary: Utility for runtime enforcement of logical assertions. !! usage: error-terminate if the assertion fails: !! !! use assertions_m, only : assert !! call assert( 2 > 1, "2 > 1") !! - !! Turn off assertions in production code by setting USE_ASSERTIONS to .false. via the preprocessor. + !! Assertion enforcement is controlled via the `ASSERTIONS` preprocessor macro, + !! which can be defined to non-zero or zero at compilation time to + !! respectively enable or disable runtime assertion enforcement. + !! + !! When the `ASSERTIONS` preprocessor macro is not defined to any value, + !! the default is that assertions are *disabled* and will not check the condition. + !! + !! Disabling assertion enforcement may eliminate any associated runtime + !! overhead by enabling optimizing compilers to ignore the assertion procedure + !! body during a dead-code-removal phase of optimization. + !! + !! To enable assertion enforcement (e.g., for a debug build), define the preprocessor ASSERTIONS to non-zero. !! This file's capitalized .F90 extension causes most Fortran compilers to preprocess this file so - !! that building as follows turns off assertion enforcement: + !! that building as follows enables assertion enforcement: !! - !! fpm build --flag "-DUSE_ASSERTIONS=.false." + !! fpm build --flag "-DASSERTIONS" !! - !! Doing so may eliminate any associated runtime overhead by enabling optimizing compilers to ignore - !! the assertion procedure body during a dead-code-removal phase of optimization. implicit none private public :: assert #ifndef USE_ASSERTIONS -# define USE_ASSERTIONS .true. +# if ASSERTIONS +# define USE_ASSERTIONS .true. +# else +# define USE_ASSERTIONS .false. +# endif #endif logical, parameter :: enforce_assertions=USE_ASSERTIONS - !! Turn off assertions as follows: fpm build --flag "-DUSE_ASSERTIONS=.false." interface From 492d7407c9c91ec19787f399414c31e2f9e68d08 Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Thu, 3 Oct 2024 13:16:28 -0700 Subject: [PATCH 4/7] Update ford CI with include path --- .github/workflows/deploy-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index e63bd4e..1806a06 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -23,8 +23,8 @@ jobs: - name: Build Developer Documentation run: | + ford -I include doc-generator.md > ford_output.txt # Turn warnings into errors - ford doc-generator.md > ford_output.txt cat ford_output.txt; if grep -q -i Warning ford_output.txt; then exit 1; fi cp ./README.md ./doc/html From fa52525c0ba3f09756ab0ea1d4657c380a61d627 Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Wed, 2 Oct 2024 19:41:25 -0700 Subject: [PATCH 5/7] README: Update documentation for change in control knob --- README.md | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a23788f..3859761 100644 --- a/README.md +++ b/README.md @@ -15,21 +15,32 @@ This assertion utility contains four public entities: 1. An `assert` subroutine, 2. A `characterizable_t` abstract type supporting `assert`, and 3. An `intrinsic_array_t` non-abstract type extending `characterizable_t`. -4. A `assert_macros.h` header file containing C-preprocessor macros. +4. An `assert_macros.h` header file containing C-preprocessor macros. The `assert` subroutine -* Error-terminates with a variable stop code when a user-defined logical assertion fails, +* Error-terminates with a variable stop code when a caller-provided logical assertion fails, * Includes user-supplied diagnostic data in the output if provided by the calling procedure, * Is callable inside `pure` procedures, and -* Can be eliminated at compile-time. +* Can be eliminated at compile-time, as controlled by the `ASSERTIONS` preprocessor define. +Assertion enforcement is controlled via the `ASSERTIONS` preprocessor macro, +which can be defined to non-zero or zero at compilation time to +respectively enable or disable run-time assertion enforcement. + +When the `ASSERTIONS` preprocessor macro is not defined to any value, +the default is that assertions are *disabled* and will not check the condition. + +To enable assertion enforcement (e.g., for a debug build), define the +preprocessor ASSERTIONS to non-zero, eg: +``` +fpm build --flag "-DASSERTIONS" +``` The program [example/invoke-via-macro.F90] demonstrates the preferred way to invoke the `assert` subroutine via the three provided macros. -Invoking `assert` this way insures that `assert` invocations will be completely removed whenever the `DEBUG` macro is set during compilation. +Invoking `assert` this way insures that `assert` invocations 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 the user must invoke `assert` directly (via `call assert(...)`). -If invoked directly, the user can set pass `-DUSE_ASSERTIONS=.false.` at compile time. -The latter approach cause `assert` to start and end with `if (.false.) then ... end if`, which might facilitate automatic removal of `assert` during the dead-code removal phase of optimizing compilers. +In the latter approach when the assertions are disabled, the `assert` procedure will start and end with `if (.false.) then ... end if`, which might facilitate automatic removal of `assert` during the dead-code removal phase of optimizing compilers. The `characterizable_t` type defines an `as_character()` deferred binding that produces `character` strings for use as diagnostic output from a user-defined derived type that extends `characterizable_t` and implements the deferred binding. @@ -50,7 +61,7 @@ The requirements and assurances might be constraints of three kinds: 2. **Postconditions (assurances):** expressions that must evaluate to `.true.` when a procedure finishes execution, and 3. **Invariants:** universal pre- and postconditions that must always be true when all procedures in a class start or finish executing. -The [examples/README.md] file shows examples of writing constraints in notes on class diagrams using the formal syntax of the Object Constraint Language ([OCL]). +The [example/README.md] file shows examples of writing constraints in notes on class diagrams using the formal syntax of the Object Constraint Language ([OCL]). Downloading, Building, and Running Examples ------------------------------------------- @@ -65,14 +76,14 @@ cd assert #### Single-image (serial) execution The following command builds Assert and runs the full test suite in a single image: ``` -fpm test --profile release +fpm test --profile release --flag "-ffree-line-length-0" ``` -which builds the Assert library and runs the test suite. +which builds the Assert library (with the default of assertion enforcement disabled) and runs the test suite. #### Multi-image (parallel) execution With `gfortran` and OpenCoarrays installed, ``` -fpm test --compiler caf --profile release --runner "cafrun -n 2" +fpm test --compiler caf --profile release --runner "cafrun -n 2" --flag "-ffree-line-length-0" ``` To build and test with the Numerical Algorithms Group (NAG) Fortran compiler version 7.1 or later, use @@ -87,7 +98,6 @@ fpm test --compiler ifx --profile release --flag -coarray ### Building and testing with the LLVM `flang-new` compiler ``` fpm test --compiler flang-new --flag "-mmlir -allow-assumed-rank -O3" - ``` ### Building and testing with the Numerical Algorithms Group (NAG) compiler From b6157fc42c000327ef19dd8f4fb937a2f28e6f8f Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Thu, 3 Oct 2024 14:49:22 -0700 Subject: [PATCH 6/7] Update macro line continuation recommendation gfortran's preprocessor is happy with a blank line break, but ifx insists on a backslash (which gfortran also accepts). So update recommended best practice to backslash continuation --- README.md | 14 +++++++------- test/test-assert-macro.F90 | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3859761..60a8136 100644 --- a/README.md +++ b/README.md @@ -197,9 +197,9 @@ Instead when breaking long lines in a macro invocation, just break the line (no continuation character!), eg: ```fortran -! When breaking a lines in a macro invocation, just use new-line with no `&` continuation character: -call_assert_diagnose( computed_checksum == expected_checksum, - "Checksum mismatch failure!", +! When breaking a lines in a macro invocation, use backslash `\` continuation character: +call_assert_diagnose( computed_checksum == expected_checksum, \ + "Checksum mismatch failure!", \ expected_checksum ) ``` @@ -223,8 +223,8 @@ comment (because they are removed by the preprocessor), for example with gfortran one can instead write the following: ```fortran -call_assert_diagnose( computed_checksum == expected_checksum, /* ensured since version 3.14 */ - "Checksum mismatch failure!", /* TODO: write a better message here */ +call_assert_diagnose( computed_checksum == expected_checksum, /* ensured since version 3.14 */ \ + "Checksum mismatch failure!", /* TODO: write a better message here */ \ computed_checksum ) ``` @@ -233,8 +233,8 @@ When in doubt, one can always move the comment outside the macro invocation: ```fortran ! assert a property ensured since version 3.14 -call_assert_diagnose( computed_checksum == expected_checksum, - "Checksum mismatch failure!", +call_assert_diagnose( computed_checksum == expected_checksum, \ + "Checksum mismatch failure!", \ computed_checksum ) ! TODO: write a better message above ``` diff --git a/test/test-assert-macro.F90 b/test/test-assert-macro.F90 index 23fc603..7384141 100644 --- a/test/test-assert-macro.F90 +++ b/test/test-assert-macro.F90 @@ -44,13 +44,13 @@ program test_assert_macros block integer :: computed_checksum = 37, expected_checksum = 37 - call_assert_diagnose( computed_checksum == expected_checksum, - "Checksum mismatch failure!", - expected_checksum ) + call_assert_diagnose( computed_checksum == expected_checksum, \ + "Checksum mismatch failure!", \ + expected_checksum ) print *," passes with macro-style line breaks" - call_assert_diagnose( computed_checksum == expected_checksum, /* ensured since version 3.14 */ - "Checksum mismatch failure!", /* TODO: write a better message here */ + call_assert_diagnose( computed_checksum == expected_checksum, /* ensured since version 3.14 */ \ + "Checksum mismatch failure!", /* TODO: write a better message here */ \ computed_checksum ) print *," passes with C block comments embedded in macro" From 22080ee7d75c127d8907db9d91f19f6b8ffe88d0 Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Fri, 4 Oct 2024 15:07:38 -0400 Subject: [PATCH 7/7] Update README.md Co-authored-by: Damian Rouson --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 60a8136..e39ba10 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ Instead when breaking long lines in a macro invocation, just break the line (no continuation character!), eg: ```fortran -! When breaking a lines in a macro invocation, use backslash `\` continuation character: +! When breaking a line in a macro invocation, use backslash `\` continuation character: call_assert_diagnose( computed_checksum == expected_checksum, \ "Checksum mismatch failure!", \ expected_checksum )