Skip to content

Support for fixed-width integers#338

Open
stefano-zanotti-88 wants to merge 7 commits intocharlesnicholson:mainfrom
stefano-zanotti-88:fixed-width-int
Open

Support for fixed-width integers#338
stefano-zanotti-88 wants to merge 7 commits intocharlesnicholson:mainfrom
stefano-zanotti-88:fixed-width-int

Conversation

@stefano-zanotti-88
Copy link
Contributor

@stefano-zanotti-88 stefano-zanotti-88 commented May 25, 2025

This adds support for the "wN" and "wfN" length modifiers.

The standard:

wN Specifies that a following b, d, i, o, u, x, or X conversion specifier applies to an integer
argument with a specific width where N is a positive decimal integer with no leading
zeros (the argument will have been promoted according to the integer promotions, but
its value shall be converted to the unpromoted type); or that a following n conversion
specifier applies to a pointer to an integer type argument with a width of N bits. All
minimum-width integer types (7.22.1.2) and exact-width integer types (7.22.1.1) defined
in the header <stdint.h> shall be supported. Other supported values of N are
implementation-defined.
wfN Specifies that a following b, d, i, o, u, x, or X conversion specifier applies to a fastest
minimum-width integer argument with a specific width where N is a positive decimal
integer with no leading zeros (the argument will have been promoted according to
the integer promotions, but its value shall be converted to the unpromoted type); or
that a following n conversion specifier applies to a pointer to a fastest minimum-width
integer type argument with a width of N bits. All fastest minimum-width integer types
(7.22.1.3) defined in the header <stdint.h> shall be supported. Other supported values
of N are implementation-defined.

So:

  1. The 'least' types are completely useless, unless an implementation decides for some obscure reason to define them (possibly with a larger width) and not define the regular [u]int<N>_t types. In NPF, I've assumed that they do exist.
  2. There is no need to support the new bit-precise [unsigned] _BitInt(N) types, which would be impossible to do in a standard-conforming way, since there can be arbitrarily many such types (it seems that clang supports up to N = 2**24), and since they do not impose the same alignment restrictions as [u]int<N>_t do, and there is no way to distinguish [u]int<N>_t from _BitInt(N), in a printf call.
    I'm mentioning this because _BitInt(N) "should" be treated like any other integer:
Any statement in this document about unsigned integer types also applies to the bit-precise unsigned integer types and
the extended unsigned integer types, unless otherwise specified.

though the printf specs explicitly mentions only the traditional fixed-width integers.
Indeed, GCC seems to support printing only of [u]int<N>_t and similar; it doesn't parse N as valid, for N values that are not in [8, 16, 32, 64]. It does not support 128 either, though there are extension types __[u]int128
See also the C proposal N2858 (mentioned in #126) -- it seems to imply that, indeed, _BitInt(N) is outside of the scope of w<N> and wf<N>, since it would get its own wb<N>.

In summary, with this PR, NPF supports:
w8, w16, w32, w64
wf8, wf16, wf32, w6f4

Here are the test cases I checked it against. I haven't integrated them in the tests for now.
The require_conform function is the same used in conformance.cc.
To test the %n specifier, I've used a different one, which also tests against buffer overrun (ie the writeback uses a pointer of the wrong type), which would be useful also for the %n tests that already exist in conformance.cc.

#define require_conform_spec_n(...)    require_conform_spec_n_helper(__LINE__, __VA_ARGS__)

typedef struct
{
  char pad_pre[64];
  int_fast8_t   n_wf8;
  int_fast16_t  n_wf16;
  int_fast32_t  n_wf32;
  int_fast64_t  n_wf64;
  int_least8_t  n_w8;
  int_least16_t n_w16;
  int_least32_t n_w32;
  int_least64_t n_w64;
  char pad_post[64];
} p_data_t;
static p_data_t p_data;
static p_data_t p_data_exp;

static
void init_p_data(void) {
  // With memset, we also init the compiler-inserted padding between member
  // variables of the struct. Testing this padding too, is necessary to ensure
  // that %n does not write where it shouldn't.
  memset(&p_data, 0xA6, sizeof(p_data));
  memset(&p_data_exp, 0xA6, sizeof(p_data_exp));
}

void require_conform_spec_n_helper(int line, const char* expected, char const *fmt, ...) {
  char buf_npf[256];
  int n_npf;

  {
    va_list args;
    va_start(args, fmt);
    n_npf = npf_vsnprintf(buf_npf, sizeof(buf_npf), fmt, args);
    va_end(args);
    buf_npf[sizeof(buf_npf)-1] = '\0';
  }

  if(0 != strcmp(buf_npf, expected)) {
    printf("fail exp @%d: |%s|%s|\n", line, buf_npf, expected);
  }

  int n_found = strlen(buf_npf);
  if(n_npf != n_found) {
    printf(
      "fail %s-len @%d: |%s|%s|, ret %d, found %d\n",
      line,
      buf_npf,
      expected,
      n_npf,
      n_found
    );
  }
  if (0 != memcmp(&p_data, &p_data_exp, sizeof(p_data))) {
    printf("fail writeback @%d\n", line);
  }
}

void test_wn(void)
{
#if NANOPRINTF_USE_FIXED_WIDTH_FORMAT_SPECIFIERS == 1
  require_conform("0", "%w8i", 0);
  require_conform("1", "%w8i", 1);
  require_conform("127", "%w8i", 127);
  require_conform("-1", "%w8i", 128);

  require_conform("0", "%w16i", 0);
  require_conform("1", "%w16i", 1);
  require_conform("32767", "%w16i", 32767);
  require_conform("-1", "%w16i", 32768);

  require_conform("0", "%w32i", 0);
  require_conform("1", "%w32i", 1);
  require_conform("2147483647", "%w32i", 2147483647);
  require_conform("-1", "%w32i", -1);

  require_conform("0", "%w64i", 0);
  require_conform("1", "%w64i", 1);
  require_conform("9223372036854775807", "%w64i", 9223372036854775807ll);
  require_conform("-1", "%w64i", -1ll);

  ////
  require_conform("0", "%w8u", 0);
  require_conform("1", "%w8u", 1);
  require_conform("127", "%w8u", 127);
  require_conform("128", "%w8u", 128);

  require_conform("0", "%w16u", 0);
  require_conform("1", "%w16u", 1);
  require_conform("32767", "%w16u", 32767);
  require_conform("32768", "%w16u", 32768);

  require_conform("0", "%w32u", 0);
  require_conform("1", "%w32u", 1);
  require_conform("2147483647", "%w32u", 2147483647);
  require_conform("2147483648", "%w32u", 2147483648u);

  require_conform("0", "%w64u", 0);
  require_conform("1", "%w64u", 1);
  require_conform("9223372036854775807", "%w64u", 9223372036854775807ll);
  require_conform("9223372036854775808", "%w64u", 9223372036854775808ull);

  ////
  require_conform("0", "%w8i", 0+256);
  require_conform("1", "%w8i", 1+256);
  require_conform("-1", "%w8i", 128+256);

  require_conform("0", "%w16i", 0+65536);
  require_conform("1", "%w16i", 1+65536);
  require_conform("-1", "%w16i", 32768+65536);

  ////
  require_conform("0", "%w8x", 0+256);
  require_conform("1", "%w8x", 1+256);
  require_conform("80", "%w8x", 128+256);

  require_conform("0", "%w16x", 0+65536);
  require_conform("1", "%w16x", 1+65536);
  require_conform("8000", "%w16x", 32768+65536);

  ////

  if(sizeof(int_fast8_t) * CHAR_BIT == 8) {
    require_conform("0", "%wf8i", (int_fast8_t)0);
    require_conform("1", "%wf8i", (int_fast8_t)1);
    require_conform("127", "%wf8i", (int_fast8_t)127);
    require_conform("-1", "%wf8i", (int_fast8_t)-1);

    require_conform("0", "%wf8u", (uint_fast8_t)0);
    require_conform("1", "%wf8u", (uint_fast8_t)1);
    require_conform("127", "%wf8u", (uint_fast8_t)127);
    require_conform("128", "%wf8u", (uint_fast8_t)128);
  } else if(sizeof(int_fast8_t) * CHAR_BIT == 16) {
    require_conform("0", "%wf8i", (int_fast8_t)0);
    require_conform("1", "%wf8i", (int_fast8_t)1);
    require_conform("32767", "%wf8i", (int_fast8_t)32767);
    require_conform("-1", "%wf8i", (int_fast8_t)-1);

    require_conform("0", "%wf8u", (uint_fast8_t)0);
    require_conform("1", "%wf8u", (uint_fast8_t)1);
    require_conform("32767", "%wf8u", (uint_fast8_t)32767);
    require_conform("32768", "%wf8u", (uint_fast8_t)32768u);
  } else if(sizeof(int_fast8_t) * CHAR_BIT == 32) {
    require_conform("0", "%wf8i", (int_fast8_t)0);
    require_conform("1", "%wf8i", (int_fast8_t)1);
    require_conform("2147483647", "%wf8i", (int_fast8_t)2147483647);
    require_conform("-1", "%wf8i", (int_fast8_t)-1);

    require_conform("0", "%wf8u", (uint_fast8_t)0);
    require_conform("1", "%wf8u", (uint_fast8_t)1);
    require_conform("2147483647", "%wf8u", (uint_fast8_t)2147483647);
    require_conform("2147483648", "%wf8u", (uint_fast8_t)2147483648u);
  } else {
    printf("Unsupported [u]int_fast8_t\n");
  }

  if(sizeof(int_fast16_t) * CHAR_BIT == 16) {
    require_conform("0", "%wf16i", (int_fast16_t)0);
    require_conform("1", "%wf16i", (int_fast16_t)1);
    require_conform("32767", "%wf16i", (int_fast16_t)32767);
    require_conform("-1", "%wf16i", (int_fast16_t)-1);

    require_conform("0", "%wf16u", (uint_fast16_t)0);
    require_conform("1", "%wf16u", (uint_fast16_t)1);
    require_conform("32767", "%wf16u", (uint_fast16_t)32767);
    require_conform("32768", "%wf16u", (uint_fast16_t)32768u);
  } else if(sizeof(int_fast16_t) * CHAR_BIT == 32) {
    require_conform("0", "%wf16i", (int_fast16_t)0);
    require_conform("1", "%wf16i", (int_fast16_t)1);
    require_conform("2147483647", "%wf16i", (int_fast16_t)2147483647);
    require_conform("-1", "%wf16i", (int_fast16_t)-1);

    require_conform("0", "%wf16u", (uint_fast16_t)0);
    require_conform("1", "%wf16u", (uint_fast16_t)1);
    require_conform("2147483647", "%wf16u", (uint_fast16_t)2147483647);
    require_conform("2147483648", "%wf16u", (uint_fast16_t)2147483648u);
  } else {
    printf("Unsupported [u]int_fast16_t\n");
  }

  if(sizeof(int_fast32_t) * CHAR_BIT == 32) {
    require_conform("0", "%wf32i", (int_fast32_t)0);
    require_conform("1", "%wf32i", (int_fast32_t)1);
    require_conform("2147483647", "%wf32i", (int_fast32_t)2147483647);
    require_conform("-1", "%wf32i", (int_fast32_t)-1);

    require_conform("0", "%wf32u", (uint_fast32_t)0);
    require_conform("1", "%wf32u", (uint_fast32_t)1);
    require_conform("2147483647", "%wf32u", (uint_fast32_t)2147483647);
    require_conform("2147483648", "%wf32u", (uint_fast32_t)2147483648u);
  } else if(sizeof(int_fast32_t) * CHAR_BIT == 64) {
    require_conform("0", "%wf32i", (int_fast32_t)0);
    require_conform("1", "%wf32i", (int_fast32_t)1);
    require_conform("9223372036854775807", "%wf32i", (int_fast32_t)9223372036854775807ll);
    require_conform("-1", "%wf32i", (int_fast32_t)-1);

    require_conform("0", "%wf32u", (uint_fast32_t)0);
    require_conform("1", "%wf32u", (uint_fast32_t)1);
    require_conform("9223372036854775807", "%wf32u", (uint_fast32_t)9223372036854775807ull);
    require_conform("9223372036854775808", "%wf32u", (uint_fast32_t)9223372036854775808ull);
  } else {
    printf("Unsupported [u]int_fast32_t\n");
  }

  if(sizeof(int_fast64_t) * CHAR_BIT == 64) {
    require_conform("0", "%wf64i", (int_fast32_t)0);
    require_conform("1", "%wf64i", (int_fast32_t)1);
    require_conform("9223372036854775807", "%wf64i", (int_fast32_t)9223372036854775807ll);
    require_conform("-1", "%wf64i", (int_fast32_t)-1);

    require_conform("0", "%wf64u", (uint_fast32_t)0);
    require_conform("1", "%wf64u", (uint_fast32_t)1);
    require_conform("9223372036854775807", "%wf64u", (uint_fast32_t)9223372036854775807ll);
    require_conform("9223372036854775808", "%wf64u", (uint_fast32_t)9223372036854775808ull);
  } else {
    printf("Unsupported [u]int_fast64_t\n");
  }
#endif

  ////////////////

#if NANOPRINTF_USE_FIXED_WIDTH_FORMAT_SPECIFIERS == 1 \
    && NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1
  init_p_data(); p_data_exp.n_w8   = 3; require_conform_spec_n("abcdef", "abc%w8ndef"  , &p_data.n_w8  );
  init_p_data(); p_data_exp.n_w16  = 3; require_conform_spec_n("abcdef", "abc%w16ndef" , &p_data.n_w16 );
  init_p_data(); p_data_exp.n_w32  = 3; require_conform_spec_n("abcdef", "abc%w32ndef" , &p_data.n_w32 );
  init_p_data(); p_data_exp.n_w64  = 3; require_conform_spec_n("abcdef", "abc%w64ndef" , &p_data.n_w64 );

  init_p_data(); p_data_exp.n_wf8  = 3; require_conform_spec_n("abcdef", "abc%wf8ndef" , &p_data.n_wf8 );
  init_p_data(); p_data_exp.n_wf16 = 3; require_conform_spec_n("abcdef", "abc%wf16ndef", &p_data.n_wf16);
  init_p_data(); p_data_exp.n_wf32 = 3; require_conform_spec_n("abcdef", "abc%wf32ndef", &p_data.n_wf32);
  init_p_data(); p_data_exp.n_wf64 = 3; require_conform_spec_n("abcdef", "abc%wf64ndef", &p_data.n_wf64);
#endif
}

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