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

Fix 64 bit integer types on 32 bit archs #186

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

StefanBruens
Copy link

In case the code is built with C89 on Linux LP32, 64 bit integers should be defined as long long types. limits.h can be used to differentiate between LP64 and LP32.

In case the code is built with C89 on Linux LP32, 64 bit integers should
be defined as long long types. limits.h can be used to differentiate
between LP64 and LP32.
@lindstro
Copy link
Member

lindstro commented Oct 2, 2022

Thanks for bringing up this issue--we should be more careful about how we detect data models.

I am a little puzzled about your solution, however. In the LP32 data model, ints are 16 bits and longs are 32 bits, so ULONG_MAX > UINT_MAX would be triggered, and the data model would incorrectly be detected as LP64. In other words, LP32 would never be selected. Moreover, the nonportable long long is not necessarily provided by data models other than LLP64 and its cousins (see https://unix.org/version2/whatsnew/lp64_wp.html and https://queue.acm.org/detail.cfm?id=1165766).

We're hoping to one day switch over to C99 stdint types, with custom C89 headers that provide corresponding types. This would similarly require carefully written code that detects the data model. If there's publicly available code that achieves this in a portable way, we'd certainly be interested.

@StefanBruens
Copy link
Author

Thanks for bringing up this issue--we should be more careful about how we detect data models.

I am a little puzzled about your solution, however. In the LP32 data model, ints are 16 bits and longs are 32 bits, so ULONG_MAX > UINT_MAX would be triggered,

Actually, this should be ILP32 here.

LP32 would be e.g. Win16, but I don't think this is relevant anymore.

@lindstro
Copy link
Member

I think we need to do a better job detecting the data model, and in a portable manner. I've begun some work on this and would appreciate some feedback. I've attached a sample piece of code as a first step.

@StefanBruens We don't have access to an ILP32 system, so could you please compile and run this code and let us know whether it does the right thing for you?

#include <limits.h>
#include <stdio.h>

/* known data models */
#define ZFP_NONE   0
#define ZFP_LP32   1
#define ZFP_ILP32  2
#define ZFP_LLP64  3
#define ZFP_LP64   4
#define ZFP_ILP64  5
#define ZFP_SILP64 6

/* Boolean constants asserting integer sizes */
#define ZFP_CHAR_IS_8_BITS (CHAR_BIT == 8)
#define ZFP_SHRT_IS_16_BITS ((USHRT_MAX >> 15) == 1)
#define ZFP_SHRT_IS_32_BITS ((USHRT_MAX >> 31) == 1)
#define ZFP_SHRT_IS_64_BITS ((USHRT_MAX >> 63) == 1)
#define ZFP_INT_IS_16_BITS ((UINT_MAX >> 15) == 1)
#define ZFP_INT_IS_32_BITS ((UINT_MAX >> 31) == 1)
#define ZFP_INT_IS_64_BITS ((UINT_MAX >> 63) == 1)
#define ZFP_LONG_IS_32_BITS ((ULONG_MAX >> 31) == 1)
#define ZFP_LONG_IS_64_BITS ((ULONG_MAX >> 63) == 1)
#ifdef ULLONG_MAX
  #define ZFP_LLONG_IS_64_BITS ((ULLONG_MAX >> 63) == 1)
#else
  #define ZFP_LLONG_IS_64_BITS 0
#endif

/* form data model as integer identifier from Boolean arguments */
#define zfp_data_model_code(c8, s16, s32, s64, i16, i32, i64, l32, l64, ll64) \
  ((c8) + 2 * ((s16) + 2 * ((s32) + 2 * ((s64) + 2 * ((i16) + 2 * ((i32) + 2 * ((i64) + 2 * ((l32) + 2 * ((l64) + 2 * ((ll64)))))))))))

#define ZFP_DATA_MODEL_CODE zfp_data_model_code(\
  ZFP_CHAR_IS_8_BITS,\
  ZFP_SHRT_IS_16_BITS,\
  ZFP_SHRT_IS_32_BITS,\
  ZFP_SHRT_IS_64_BITS,\
  ZFP_INT_IS_16_BITS,\
  ZFP_INT_IS_32_BITS,\
  ZFP_INT_IS_64_BITS,\
  ZFP_LONG_IS_32_BITS,\
  ZFP_LONG_IS_64_BITS,\
  ZFP_LLONG_IS_64_BITS\
)

/* determine data model */
#if   ZFP_DATA_MODEL_CODE == zfp_data_model_code(1,  1, 0, 0,  1, 0, 0,  1, 0,  0)
  #define ZFP_DATA_MODEL ZFP_LP32
  #error "no 64-bit integer type for LP32"
#elif ZFP_DATA_MODEL_CODE == zfp_data_model_code(1,  1, 0, 0,  1, 0, 0,  1, 0,  1)
  #define ZFP_DATA_MODEL ZFP_LP32
  typedef int64 signed long long int64;
  typedef uint64 unsigned long long uint64;
#elif ZFP_DATA_MODEL_CODE == zfp_data_model_code(1,  1, 0, 0,  0, 1, 0,  1, 0,  0)
  #define ZFP_DATA_MODEL ZFP_ILP32
  #error "no 64-bit integer type for ILP32"
#elif ZFP_DATA_MODEL_CODE == zfp_data_model_code(1,  1, 0, 0,  0, 1, 0,  1, 0,  1)
  #define ZFP_DATA_MODEL ZFP_LLP64
  typedef signed long long int64;
  typedef unsigned long long uint64;
#elif ZFP_DATA_MODEL_CODE == zfp_data_model_code(1,  1, 0, 0,  0, 1, 0,  0, 1,  0) ||\
      ZFP_DATA_MODEL_CODE == zfp_data_model_code(1,  1, 0, 0,  0, 1, 0,  0, 1,  1)
  #define ZFP_DATA_MODEL ZFP_LP64
#elif ZFP_DATA_MODEL_CODE == zfp_data_model_code(1,  1, 0, 0,  0, 0, 1,  0, 1,  0) ||\
      ZFP_DATA_MODEL_CODE == zfp_data_model_code(1,  1, 0, 0,  0, 0, 1,  0, 1,  1)
  #define ZFP_DATA_MODEL ZFP_ILP64
  typedef _int32 int32;
  typedef _uint32 uint32;
#elif ZFP_DATA_MODEL_CODE == zfp_data_model_code(1,  0, 0, 1,  0, 0, 1,  0, 1,  0) ||\
      ZFP_DATA_MODEL_CODE == zfp_data_model_code(1,  0, 0, 1,  0, 0, 1,  0, 1,  1)
  #define ZFP_DATA_MODEL ZFP_SILP64
  #error "SILP64 data model not supported"
#else
  #error "unknown data model"
#endif

typedef signed char int08;
typedef unsigned char uint08;
typedef signed short int16;
typedef unsigned short uint16;
#if ZFP_DATA_MODEL != ZFP_ILP64
  typedef signed int int32;
  typedef unsigned int uint32;
#endif
#if ZFP_DATA_MODEL == ZFP_LP64 || ZFP_DATA_MODEL == ZFP_ILP64
  typedef signed long int64;
  typedef unsigned long uint64;
#endif

#define _str(x) #x
#define str(x) _str(x)

int main()
{
  const char* model[] = { "none", "LP32", "ILP32", "LLP64", "LP64", "ILP64", "SILP64" };
  printf("data model: %s\n", model[ZFP_DATA_MODEL]);
  printf("u08: %d\n", (int)(sizeof(uint08) * CHAR_BIT));
  printf("u16: %d\n", (int)(sizeof(uint16) * CHAR_BIT));
  printf("u32: %d\n", (int)(sizeof(uint32) * CHAR_BIT));
  printf("u64: %d\n", (int)(sizeof(uint64) * CHAR_BIT));
  printf("USHRT_MAX: %s\n", str(USHRT_MAX));
  printf("UINT_MAX: %s\n", str(UINT_MAX));
  printf("ULONG_MAX: %s\n", str(ULONG_MAX));
#ifdef ULLONG_MAX
  printf("ULLONG_MAX: %s\n", str(ULLONG_MAX));
#endif
  return 0;
}

@StefanBruens
Copy link
Author

You can compile and run it on any x86_64 system (provided you have 32 bit libraries installed), e.g.:
gcc -m32 /tmp/ilp.c && ./a.out

This reports: data model: LLP64

This is obviously incorrect. The reason is simple - while ILP32 may or may not have a 64 bit integer type (typically it does), the distinction between ILP32 an LLP64 is not the 64bit integer, but sizeof(void*).

@lindstro
Copy link
Member

Good point, but I don't know of anything in zfp that depends on the storage of pointers, i.e., sizeof(void*). Can you please elaborate?

@StefanBruens
Copy link
Author

Good point, but I don't know of anything in zfp that depends on the storage of pointers, i.e., sizeof(void*). Can you please elaborate?

I don't say you need it, I only wanted to point out the posted code is wrong. It does not detect LLP64 vs ILP32 correctly.

But when you look at my MR, it has #elif ZFP_LLP64 || ZFP_ILP32, so this distinction does not matter much. The only relevant distinction is the existence of a 64bit integer - while it is guaranteed to exist on LLP64, it is "only" typical/likely to exist on ILP32 (and you should be fine to ignore any platform where it does not).

@lindstro
Copy link
Member

I see. Well, I suppose it would be nice to correctly detect pointer size, but I cannot think of how to do that in a portable way as sizeof() is not available to the preprocessor and UINTPTR_MAX is not part of C89.

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

Successfully merging this pull request may close these issues.

2 participants