Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZFP Fortran Bindings and nvhpc #163

Open
henryleberre opened this issue May 31, 2022 · 23 comments
Open

ZFP Fortran Bindings and nvhpc #163

henryleberre opened this issue May 31, 2022 · 23 comments

Comments

@henryleberre
Copy link

I ran into a few hurdles while attempting to compile and use ZFP with Cuda support using NVHPC v22.5 and its Fortran bindings. I might have misinterpreted part of the documentation, and would appreciate your help, especially with my last issue which I haven't yet resolved.

System details

Building ZFP

  • The CMake options bellow didn't change which host compiler was used. However, it does work if I export the $CUDAHOSTCXX environment variable or specify -DCMAKE_CUDA_FLAGS="--compiler-bindir \"$(which nvc++)\".

    • -DCMAKE_CUDA_COMPILER_ID=NVHPC
    • -DCMAKE_CUDA_HOST_COMPILER="$(which nvc++)"
  • Without -DCMAKE_Fortran_FLAGS="-Mfree", an error (sometimes) occurs while linking zFORp. It may require an older version of NVHPC or a specific computer (mis)configuration to reproduce. I couldn't reproduce it. Some systems may have prepended it to the $FFLAGS environment variable.

  • The commit c367213 brakes compatibility for me with NVHPC, citing the following error:

NVFORTRAN-S-0087-Non-constant expression where constant expression required (/home/henrylb/zfp/fortran/zfp.f90: 458)
NVFORTRAN-S-0087-Non-constant expression where constant expression required (/home/henrylb/zfp/fortran/zfp.f90: 464)
NVFORTRAN-S-0087-Non-constant expression where constant expression required (/home/henrylb/zfp/fortran/zfp.f90: 470)
NVFORTRAN-S-0087-Non-constant expression where constant expression required (/home/henrylb/zfp/fortran/zfp.f90: 476)

The error pertains to c_ptrdiff_t which NVHPC doesn't recognize correctly. From my understanding, it was part of the TS 29113 extension (source) which NVIDIA should support. Courtesy of @sbryngelson, here is a minimal reproduceable example:

program zFORp

    use, intrinsic :: iso_c_binding, only: c_ptrdiff_t, c_ptr
    implicit none

    
    integer(c_ptrdiff_t) :: sx

    print *, 'test'

    stop

end program

@sbryngelson verified that the above code does compile and run on both GNU and Intel compilers, so this issue is specific to NVHPC.

To get ZFP building, I replaced these lines with:

integer(c_int) ...

However, the size of this type is most certainly not appropriate (32 vs 64 bits). I'm not currently using these functions should I proceeded with this temporary fix.

Here are the final build commands I used after some troubleshooting. Some options might be redundant.

$ git clone https://github.com/LLNL/zfp.git
$ mkdir -p zfp/build && cd zfp/build
$ cmake .. -DZFP_WITH_CUDA=ON -DBUILD_ZFORP=ON -DBUILD_TESTING=OFF -DBUILD_UTILITIES=OFF \
       -DZFP_WITH_OPENMP=ON -DCUDA_NVCC_EXECUTABLE="$(which nvcc)" \
       -DCMAKE_Fortran_FLAGS="-Mfree" -DCMAKE_CUDA_HOST_COMPILER="$(which nvc++)" \
       -DCMAKE_CUDA_FLAGS="--compiler-bindir \"$(which nvc++)\" \
       -DCMAKE_INSTALL_PREFIX="$(pwd)"
$ cmake --build . --config Release -j $(nproc)

Running ZFP

I haven't yet resolved this last issue, and would greatly appreciate your guidance. I wrote two test programs (in C++ and Fortran respectively), that benchmark compression and decompression with different buffer sizes, execution engines, and fixed-compression rates, writing the results to a file. These codes are available here.

The C version manages to to activate the available execution policies using:

zfp_stream* const stream = zfp_stream_open(NULL);
if (zfp_stream_set_execution(stream, policy) == 1) {
    printf("    - %d engine available. Activated.\n", policy);
} else {
    printf("    - %d not available..\n", policy);
    return;
}

I compiled and ran it with the bellow commands. (I cloned zfp into the root of my benchmarking code's directory).

$ nvc++ -Izfp/include -O3 main.cpp -o cpp -lzfp -Lzfp/build/lib
$ LD_LIBRARY_PATH="zfp/build/lib:$LD_LIBRARY_PATH" ./cpp

However, the Fortran version fails on all policies, including zFORp_exec_serial using this code:

type(zFORp_field)     :: field
type(zFORp_stream)    :: stream
type(zFORp_bitstream) :: bitstream
integer(c_int), dimension(3) :: POLICIES = (/ zFORp_exec_serial, zFORp_exec_omp, zFORp_exec_cuda /)
! ...

do i = 1, size(POLICIES)
  field = zFORp_field_1d(pOriginal, zFORp_type_double, INT(LEN))
  stream = zFORp_stream_open(bitstream)
  
  actual_zfp_rate = zFORp_stream_set_rate(stream, zfp_rate, zFORp_type_double, 1, 0)
  
  MAX_SIZE_compressed = zFORp_stream_maximum_size(stream, field)
  
  ! ... (Allocate Buffers)
  
  bitstream = zFORp_bitstream_stream_open(pCompressed, MAX_SIZE_compressed)
  call zFORp_stream_set_bit_stream(stream, bitstream)
  call zFORp_stream_rewind(stream)
  
  if (zFORp_stream_set_execution(stream, POLICIES(i)) .eq. 0) then
      print '(" [INIT] ZFP Execution Policy "I0" Unsupported.")', POLICIES(i)
      ! ...
      cycle
  end if
  ! ...
end do

Compiled with cmake .. && make -j 8 and this CMakeLists.txt file. I run it using

$ LD_LIBRARY_PATH="zfp/build/lib:$LD_LIBRARY_PATH" ./build/zfp_experiment

The only Fortran example I could find doesn't use this function. I am new to Fortran (my background is in C++) so I might be candidly overseeing an obvious error.

@henryleberre
Copy link
Author

henryleberre commented Jul 8, 2022

Update: I looked into it some more today, intercepting the calls in Fortran and in C. Calling zFORp_stream_set_execution, the received arguments (in the Fortran interface) are correct but, when they are forwarded to C, I receive nonsensical values. Reinterpreting them as uint64_t (or int64_t) as looking at their binary representation doesn't provide more insight. The size of c_int and the types representing the enums are the same in C and Fortran.

@lindstro
Copy link
Member

lindstro commented Aug 1, 2022

@henryleberre Apologies for a very late response--I did not get notifications for this issue and just now saw it.

c_ptrdiff_t requires Fortran 2018 support. Perhaps that's the issue? This has been documented in today's 1.0.0 release.

Does the Fortran test in tests/fortran/testFortran.f pass?

In your example, stream = zFORp_stream_open(bitstream) is called before bitstream has even been allocated (via zFORp_bitstream_stream_open(), which may be the cause. See the simple.c example for the order of calls to make.

@henryleberre
Copy link
Author

@lindstro There is no need to apologize, thank you for responding. I had used tests/fortran/testFortran.f as a reference and do confirm it worked properly. However, adding a call to the Fortran equivalent of zfp_stream_set_execution in this file does produce the same behavior as with my tests.

Indeed, the example I posted does seem to contain an error. While attempting to create this abridged excerpt I must have misplaced a few lines. The actual code tested different policies, buffer sizes, and rates. I had both C and Fortran versions which had identical structure, but the Fortran one would always fail when setting the execution policy. I had traced through the various layers of the API calls (from the user-facing Fortran function, to the Fortran interface, finally to the C function), and noticed the execution policy received by C would always be wrong. I did not check, however, if the handle to the stream was accurate. But in my testing, the execution policy was correctly captured except when it arrived to C.

I circumvented this issue by creating my own C wrapper around ZFP, and creating a Fortran interface for it. I also haven't checked whether recent compiler releases have addressed some of these issues. Interestingly, NVIDIA does somewhat tacitly advertise Fortran 2018 support (though only the "Language" section):

Compatibility and Conformance to Standards
Your system needs to be running a properly installed and configured version of the NVIDIA HPC compilers. For information on installing NVIDIA HPC compilers, refer to the Release Notes and Installation Guide included with your software. For further information, refer to the following:

  • [...]
  • ISO/IEC 1539-1 : 2018, Information technology – Programming Languages – Fortran, Geneva, 2018 (Fortran 2018).
  • [...]

The impression I had after talking to a contact at NVIDIA was that they simply hadn't gotten around to supporting c_ptrdiff_t.

@lindstro
Copy link
Member

lindstro commented Aug 2, 2022

@henryleberre I can't think off the top of my head why possible lack of c_ptrdiff_t support would interfere with the execution policy, but admittedly we do not yet have adequate testing of the Fortran bindings. Come to think of it, we recently (around June 22) changed the zfp_stream struct on develop to separate out the execution parameters to a dynamically allocated struct (pointed to by void*). It's possible that this may have had unintended consequences for the Fortran API, but your issue precedes that change. Let us take a closer look over the coming days and get back to you.

@henryleberre
Copy link
Author

@lindstro I agree, I don't think c_ptrdiff_t is the culprit. Thank you for taking a deeper look into this!

@lindstro
Copy link
Member

lindstro commented Aug 2, 2022

No problem. I also found out that, after being a member for over a decade, GitHub decided that my email address was all of a sudden unverified, which presumably is why I've not been getting notifications (though for some reason I'm getting CI and commit status messages). Go figure! Sorry again about the delay.

@lindstro
Copy link
Member

lindstro commented Sep 2, 2022

@henryleberre I've not forgotten about you--we're just dealing with a lot of issues after the 1.0.0 release.

After some effort, I was able to build your main.cpp, which gives this output:

% ./cpp.sh
+ nvc++ -I./include -L./lib64 -I/usr/tce/packages/cuda/cuda-11.1.0/include -L/usr/tce/packages/cuda/cuda-11.1.0/lib64 -O3 main.cpp -o cpp -lcudart -lzfp
nvc++-Warning-CUDA_HOME has been deprecated. Please, use NVHPC_CUDA_HOME instead.
+ LD_LIBRARY_PATH=./lib64:/usr/tce/packages/cuda/cuda-11.1.0/lib64
+ ./cpp
LEN 100
    - 2 100 engine available. Activated.
00000000000000000000LEN 1000
    - 2 1000 engine available. Activated.
00000000000000000000LEN 10000
    - 2 10000 engine available. Activated.
00000000000000000000LEN 100000
    - 2 100000 engine available. Activated.
00000000000000000000LEN 1000000
    - 2 1000000 engine available. Activated.
00000000000000000000LEN 10000000
    - 2 10000000 engine available. Activated.
00000000000000000000

I'm, however, unable to build main.f90:

% ./fc.sh
+ set -x
+ nvfortran -I./include -L./lib64 -I/usr/tce/packages/cuda/cuda-11.1.0/include -L/usr/tce/packages/cuda/cuda-11.1.0/lib64 -O3 main.f90 -o fc -lcudart -lzFORp -lzfp
nvfortran-Warning-CUDA_HOME has been deprecated. Please, use NVHPC_CUDA_HOME instead.
NVFORTRAN-F-0004-Corrupt or Old Module file ./include/zfp.mod (main.f90: 7)
NVFORTRAN/power Linux 22.7-0: compilation aborted

I'm guessing this has something to do with gfortran being used to build zFORp and nvfortran building main.f90. When pointing CMAKE_Fortran_COMPILER at nvfortran, zfp does not build:

NVFORTRAN-S-0087-Non-constant expression where constant expression required (/g/g19/pl/dev/fortran/zfp.f90: 467)
NVFORTRAN-S-0087-Non-constant expression where constant expression required (/g/g19/pl/dev/fortran/zfp.f90: 473)
NVFORTRAN-S-0087-Non-constant expression where constant expression required (/g/g19/pl/dev/fortran/zfp.f90: 479)
NVFORTRAN-S-0087-Non-constant expression where constant expression required (/g/g19/pl/dev/fortran/zfp.f90: 485)

As you already pointed out, these errors have to do with integer(c_ptrdiff_t).

I'm not sure how to proceed here. This seems more of a NVHPC issue than a zfp one. ptrdiff_t should be supported as of Fortran 2018. I'll tinker some more with it to see if I can find a workaround.

@henryleberre
Copy link
Author

Thank you @lindstro for looking into this! As you mentioned, this is an issue with NVHPC, not with ZFP. We would all benefit from it being fixed on the compiler side, rather than having to resort to approximations. I posted on the NVHPC forum about this problem in hopes that they will consider supporting these additional interoperability features in a future release: https://forums.developer.nvidia.com/t/f2018-ts-29113-compliance/227197. The gfortran issue you faced stems from Fortran module files - unfortunately - not being compatible across compilers.

@jeffhammond
Copy link

jeffhammond commented Sep 6, 2022

NVHPC Fortran does not claim support for TS 29113 nor do we support it. However, if all you need is ptrdiff_t, why not just use c_size_t or c_int64_t?

Anyways, I'll reproduce and try to fix tomorrow. Unless I've missed something, there's a trivial workaround.

Also, feel free to tag me in the future when you have NVHPC issues.

@lindstro
Copy link
Member

lindstro commented Sep 6, 2022

The problem is that we're using iso_c_binding to pass data between the Fortran and C implementations of zfp, so types must match (e.g., we support negative strides encoded in a ptrdiff_t). If there's a clean way of getting around that, then please let us know.

@henryleberre
Copy link
Author

henryleberre commented Sep 6, 2022

Thank you @jeffhammond for the clarification. I had replaced ptrdiff_t with a signed 64-bit integer but the arguments received by C had no resemblance to those passed in Fortran, even though the size and sign of the parameters' types did agree. This is why I assumed there was another issue at play here, somewhere during linkage.

@jeffhammond
Copy link

I'll play around with it tomorrow (I'm 10 hours ahead of you). I've spent a lot of time on CFI features lately but haven't tested everything obviously.

@henryleberre
Copy link
Author

[Copying my message from the NVHPC forum channel]

Thank you very much MatColgrove and jeffhammond for the clarification and help on this issue. I wasn't sure whether this was a bug or an unimplemented feature. Since some major F2018 features had already been implemented (particularly standard parallelism) and the ISO standard was referenced, I naïvely assumed NVFortran was an F2018 compiler. I expect most codes probably don't rely on all of the new features.

Switching to the Flang front-end is an excellent idea. My experience with Flang and the LLVM toolchain in general has been excellent. The middle-end and the back-end are what make NVHPC such a great toolkit for HPC with NVIDIA GPUs.

@jeffhammond I haven't looked into this issue in quite some time since I circumvented it by creating my own C wrapper & Fortran interface, but it would be my pleasure to help.

@jeffhammond
Copy link

jeffhammond commented Sep 7, 2022

This is what I see right now with NVHPC and GCC after applying a trivial patch. Is this success or failure in the test?

jhammond@nuclear:~/zfp/gcc$ gfortran ../tests/fortran/testFortran.f90 ../fortran/zfp.f90 -L./lib -lzfp -Wl,-rpath=./lib && ./a.out
 After compression, bitstream offset at
                   64
 After decompression, bitstream offset at
                   64
 Max absolute error:
           1
 Absolute errors:
           1           0           1           0           1           0
           1           0           1           0           1           0
           1           0           1           0           1           0
           1           0           1           0           1           0
           1           0           1           0           1           0
           1           0           1           0           1           0
           1           0           1           0           1           0
           1           0           1           0           1           0
           1           0           1           0           1           0
           1           0           1           0           1           0
           1           0           1           0
 zfp version 1.0.0 (August 1, 2022)
                   -1
jhammond@nuclear:~/zfp/nvc$ nvfortran ../tests/fortran/testFortran.f90 ../fortran/zfp.f90 -L./lib -lzfp -Wl,-rpath=./lib && ./a.out
../tests/fortran/testFortran.f90:
../fortran/zfp.f90:
 After compression, bitstream offset at
                       64
 After decompression, bitstream offset at
                       64
 Max absolute error:
            1
 Absolute errors:
            1            0            1            0            1            0
            1            0            1            0            1            0
            1            0            1            0            1            0
            1            0            1            0            1            0
            1            0            1            0            1            0
            1            0            1            0            1            0
            1            0            1            0            1            0
            1            0            1            0            1            0
            1            0            1            0            1            0
            1            0            1            0            1            0
            1            0            1            0
 zfp version 1.0.0 (August 1, 2022)
                       -1
diff --git a/fortran/zfp.f90 b/fortran/zfp.f90
index f671d14..861eaed 100644
--- a/fortran/zfp.f90
+++ b/fortran/zfp.f90
@@ -1,6 +1,6 @@
 module zfp

-  use, intrinsic :: iso_c_binding, only: c_int, c_int64_t, c_size_t, c_ptrdiff_t, c_double, c_ptr, c_null_ptr, c_loc
+  use, intrinsic :: iso_c_binding, only: c_int, c_int64_t, c_size_t, c_double, c_ptr, c_null_ptr, c_loc
   implicit none
   private

@@ -464,25 +464,25 @@ module zfp
     subroutine zfp_field_set_stride_1d(field, sx) bind(c, name="zfp_field_set_stride_1d")
       import
       type(c_ptr), value :: field
-      integer(c_ptrdiff_t) :: sx
+      integer(c_size_t) :: sx
     end subroutine

     subroutine zfp_field_set_stride_2d(field, sx, sy) bind(c, name="zfp_field_set_stride_2d")
       import
       type(c_ptr), value :: field
-      integer(c_ptrdiff_t) :: sx, sy
+      integer(c_size_t) :: sx, sy
     end subroutine

     subroutine zfp_field_set_stride_3d(field, sx, sy, sz) bind(c, name="zfp_field_set_stride_3d")
       import
       type(c_ptr), value :: field
-      integer(c_ptrdiff_t) :: sx, sy, sz
+      integer(c_size_t) :: sx, sy, sz
     end subroutine

     subroutine zfp_field_set_stride_4d(field, sx, sy, sz, sw) bind(c, name="zfp_field_set_stride_4d")
       import
       type(c_ptr), value :: field
-      integer(c_ptrdiff_t) :: sx, sy, sz, sw
+      integer(c_size_t) :: sx, sy, sz, sw
     end subroutine

     function zfp_field_set_metadata(field, encoded_metadata) result(is_success) bind(c, name="zfp_field_set_metadata")
@@ -1065,26 +1065,26 @@ contains
   subroutine zFORp_field_set_stride_1d(field, sx) bind(c, name="zforp_field_set_stride_1d")
     type(zFORp_field), intent(in) :: field
     integer, intent(in) :: sx
-    call zfp_field_set_stride_1d(field%object, int(sx, c_ptrdiff_t))
+    call zfp_field_set_stride_1d(field%object, int(sx, c_size_t))
   end subroutine zFORp_field_set_stride_1d

   subroutine zFORp_field_set_stride_2d(field, sx, sy) bind(c, name="zforp_field_set_stride_2d")
     type(zFORp_field), intent(in) :: field
     integer, intent(in) :: sx, sy
-    call zfp_field_set_stride_2d(field%object, int(sx, c_ptrdiff_t), int(sy, c_ptrdiff_t))
+    call zfp_field_set_stride_2d(field%object, int(sx, c_size_t), int(sy, c_size_t))
   end subroutine zFORp_field_set_stride_2d

   subroutine zFORp_field_set_stride_3d(field, sx, sy, sz) bind(c, name="zforp_field_set_stride_3d")
     type(zFORp_field), intent(in) :: field
     integer, intent(in) :: sx, sy, sz
-    call zfp_field_set_stride_3d(field%object, int(sx, c_ptrdiff_t), int(sy, c_ptrdiff_t), int(sz, c_ptrdiff_t))
+    call zfp_field_set_stride_3d(field%object, int(sx, c_size_t), int(sy, c_size_t), int(sz, c_size_t))
   end subroutine zFORp_field_set_stride_3d

   subroutine zFORp_field_set_stride_4d(field, sx, sy, sz, sw) bind(c, name="zforp_field_set_stride_4d")
     type(zFORp_field), intent(in) :: field
     integer, intent(in) :: sx, sy, sz, sw
-    call zfp_field_set_stride_4d(field%object, int(sx, c_ptrdiff_t), int(sy, c_ptrdiff_t), &
-                                               int(sz, c_ptrdiff_t), int(sw, c_ptrdiff_t))
+    call zfp_field_set_stride_4d(field%object, int(sx, c_size_t), int(sy, c_size_t), &
+                                               int(sz, c_size_t), int(sw, c_size_t))
   end subroutine zFORp_field_set_stride_4d

   function zFORp_field_set_metadata(field, encoded_metadata) result(is_success) bind(c, name="zforp_field_set_metadata")

@jeffhammond
Copy link

Also, if you rename this file, you no longer need free source form flags.

renamed:    ../tests/fortran/testFortran.f -> ../tests/fortran/testFortran.f90

@lindstro
Copy link
Member

lindstro commented Sep 7, 2022

@jeffhammond Thanks for your help with this. Yes, the test output is as expected, but looking at our test in detail for the first time, it is not much of a "test." The decompressed data is not really validated (other than printing the difference between arrays), and the test essentially misuses zfp's support for integer compression, not to mention that it confuses ints and floats. We'll need to clean this up and add a bit more rigor.

By the way, what's a portable way in Fortran to return success/error to the OS, which ctest expects? Without it, we would never record a failed test.

About your proposed fix, what happens if a stride is negative? The C function signature for zfp_field_set_stride_*() uses ptrdiff_t, so does the parameter conversion not assume some binary compatibility between size_t and ptrdiff_t? I'd expect that under the hood something akin to a C++ reinterpret_cast is performed. My sense is that negative strides could result in undefined behavior but would probably "work" in practice as long as sizeof(ptrdiff_t) <= sizeof(size_t). It would of course be highly unusual for this inequality not to be met, but my understanding is that the C standard allows for it.

@jeffhammond
Copy link

Fortran STOP should return numerical values. I think that is what you want for ctest but I haven't verified.

I'm not aware of any relevant system where size_t, ptrdiff_t and intptr_t aren't the same size. I also don't worry about C in theory, because all nontrivial C programs contain undefined behavior and therefore are permitted to launch nukes. I focus on the computers that exist and use 2s-complement integers.

@lindstro
Copy link
Member

lindstro commented Sep 7, 2022

I know very little about Fortran, but my understanding is that STOP does not necessarily return a value (the PGI Fortran compiler just prints the value of the STOP argument, for instance). It turns out that ctest can check for a regular expression in the output via PASS_REGULAR_EXPRESSION, which would solve this problem. I added a new issue #179 to explicitly deal with our poorly written Fortran test.

I agree it's reasonable to expect that size_t and ptrdiff_t have the same size, but I disagree with changing zfp to invoke implementation defined or undefined behavior as workaround to support one particular noncompliant compiler. zfp has been carefully written to avoid UB, and to my knowledge there's nothing in the code that invokes UB, whether intentionally or not. There are some instances where implementation defined behavior is needed for performance reasons (e.g., arithmetic right shifts); such behavior is tested in testzfp.

At any rate, the issue at hand is the problem with zFORp_stream_set_execution(), where the corresponding C function takes an enumerated type but is passed a c_int. It's unclear to me if that is safe. 6.7.2.2 of the C99 standard says:

Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration.

That said, there are numerous zfp functions that accept enumerated types, so if this fails, it should fail elsewhere, e.g. in zFORp_stream_set_rate().

@jeffhammond
Copy link

I guess I don't see where the undefined behavior comes in here. Fortran (ISO/IEC DIS 1539-1:2022) says the following:

ISO/IEC 9899:2018 specifies that the representations for nonnegative signed integers are the same as the corresponding values of unsigned integers. Because Fortran does not provide direct support for unsigned kinds of integers, the ISO_C_BINDING module does not make accessible named constants for their kind type parameter values. A user can use the signed kinds of integers to interoperate with the unsigned types and all their qualified versions as well. This has the potentially surprising side effect that the C type unsigned char is interoperable with the type integer with a kind type parameter of C_SIGNED_CHAR.

Assuming 2s-complement behavior is also safe, because every computer does that and both C and C++ have standardized it:

If you are worried about the potentially unsigned nature of size_t, you could use int64_t or long long int, potentially conditioned on 64-bit platforms if you are actually supporting 32-bit ones at this point. As NVHPC compilers don't support 32-bit Linux anymore, you could just add this to your module:

#ifdef __NVCOMPILER
integer, parameter :: ptrdiff_kind = c_int64_t
#else
integer, parameter :: ptrdiff_kind = c_ptrdiff_t
#endif

and use this everywhere:

integer(kind=ptrdiff_kind)

@jeffhammond
Copy link

Regarding C enum, you can exploit the following and define an unused enum member that is INT_MAX to force the compiler to use a type that is compatible with int.

The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration.

The other options are to write a shim for Fortran to C that safely casts from whatever Fortran uses to the enum. You could always use C_SIGNED_CHAR on the Fortran side if you know all your enums fit into 0..127, or always use C_INT and safely cast into the enum.

You can also figure out what the C compiler with your enums and select from C_SIGNED_CHAR and C_INT in Fortran as appropriate.

@lindstro
Copy link
Member

lindstro commented Sep 8, 2022

I was commenting on undefined behavior in the context of "all nontrivial C programs contain undefined behavior." :-) C90 and C99 (which are the standards zfp targets) allow signed integer trap representations, which invoke UB, and representations other than two's complement (as does C11--not sure about C17), though they are of course rare. To my knowledge, N2218 is still in proposal form and has not been adopted yet. Hence, it's possible (albeit very unlikely) that UB would occur with size_t and ptrdiff_t conversions. Conversion from size_t to ptrdiff_t can also result in overflow, which is implementation defined.

Even if these issues are or do get resolved in recent/future C standards, we've made a conscious effort to code to older standards. F2018 is an exception as we recently learned that c_ptrdiff_t is not part of older Fortran standards. With the zfp C API already fixed, we pretty much had to go with F2018.

On ILP32 systems, which we do accommodate, ptrdiff_t and int64_t aren't binary compatible. We support not only Linux but also macOS and Windows.

Thanks for the suggestion on enums. I'll see if that solves the problem.

@jeffhammond
Copy link

What I tried to say before is, I do not think you can overflow when Fortran passes INTEGER(kind=c_size_t) to a ptrdiff_t because there is no way for Fortran to access the last bit of the positive range, because Fortran doesn't support unsigned integers. But also, I'm suggesting you only do this -- or INTEGER(kind=c_int64_t) -- with a preprocess guard for NVHPC Fortran, which only supports 64-bit Linux right now.

I don't have an argument if you insist on assuming nothing more than what is in C99. All I can say is that everybody else, including the C++ committee, assumes 2s-complement, because there hasn't been a 1s-complement computer architecture designed since the 1960s.

https://herbsutter.com/page/5/

Signed integers are two’s complement (JF Bastien) is the result of a courtroom drama in both WG21 (C++) and WG14 (C). After intense prosecutorial cross-examination, the witness finally admitted in both courts that, yes, all known modern computers are two’s complement machines, and, no, we don’t really care about using C++ (or C) on the ones that aren’t. The C standard is likely to adopt the same change.

I haven't looked at the voting logs but I thought JF tweeted about the C proposal passing already.

From https://en.wikipedia.org/wiki/Signed_number_representations:

The PDP-1, CDC 160 series, CDC 3000 series, CDC 6000 series, UNIVAC 1100 series, and LINC computer use ones' complement representation.

two's complement technology was adopted in virtually all processors, including x86, m68k, Power ISA, MIPS, SPARC, ARM, Itanium, PA-RISC, and DEC Alpha.

@lindstro
Copy link
Member

lindstro commented Sep 9, 2022

What I tried to say before is, I do not think you can overflow when Fortran passes INTEGER(kind=c_size_t) to a ptrdiff_t because there is no way for Fortran to access the last bit of the positive range, because Fortran doesn't support unsigned integers.

So what happens when a negative "plain" integer is passed as argument to a Fortran function that takes an INTEGER(kind=c_size_t)? Is the last bit zeroed? Is the value clamped? Either would be disastrous. Presumably the signed integer is only "reinterpreted" as a size_t and all bits are preserved.

Now, it is unclear to me when the conversion to ptrdiff_t takes place. As far as Fortran is concerned, the C function being called takes a size_t, so no conversion attempt is made on the Fortran side. Similarly, the C function expects a ptrdiff_t and must assume that's what's being passed, so no conversion attempt is made on the C side either.

Since signatures now differ, would this not confuse the linker? There is no function that takes a size_t, only one that takes a ptrdiff_t. Evidently this is not an issue when calling C from C, but linking fails if compiling the same code as C++ due to name mangling. I've not yet tested what happens if calling C from Fortran.

If, on the other hand, conversion does happen, that's where implementation defined behavior is needed. It's not so much whether the implementation uses two's complement but rather how it responds to integer conversion. From C99 6.3.1.3:

Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

It's perfectly valid for an overflowing conversion from size_t to ptrdiff_t to result in a trap representation (e.g., 0x800...0) or to clamp to the maximum positive value (e.g., 0x7ff...f). In fact, this usually happens in overflow when converting from floating to integer types. This piece of code

#include <stddef.h>
#include <stdio.h>

int main()
{
  size_t x = -1;
  ptrdiff_t y = x;
  double z = x;
  ptrdiff_t w = z;
  printf("%#zx %td %g %td\n", x, y, z, w);
  return 0;
}

gives as output 0xffffffffffffffff -1 1.84467e+19 -9223372036854775808 on my Intel machine but 0xffffffffffffffff -1 1.84467e+19 9223372036854775807 on Power and ARM. Admittedly, all of these do the "right thing" with respect to conversion from size_t to ptrdiff_t. It would be reasonable to verify this behavior in testzfp if we decided to go with the size_t trick (in addition to checking sizeof(size_t) == sizeof(ptrdiff_t)).

I don't have an argument if you insist on assuming nothing more than what is in C99. All I can say is that everybody else, including the C++ committee, assumes 2s-complement, because there hasn't been a 1s-complement computer architecture designed since the 1960s.

Fair enough (though all C standards I'm aware of, including the latest C17 draft, explicitly allow sign-magnitude and one's complement), but I think your assumption is even stronger: that integer conversions do nothing but reinterpret the binary representation (i.e., in addition to using two's complement for signed integers). That may also be a good assumption when two's complement is used since then it makes perfect sense. But in general I think those are orthogonal issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants