@@ -410,16 +410,24 @@ One feature can be enabled by specifying `--enable-FEATURE=yes' or
invoke clean-up functions such as _fini or global destructors.
Disabled by default.
+`--enable-newlib-nano-io-long-long'
+ Enable long long output in nano printf-family functions.
+ Nano formatted I/O is unchanged when this option is disabled.
+ Disabled by default.
+
`--enable-newlib-nano-formatted-io'
This builds NEWLIB with a special implementation of formatted I/O
functions, designed to lower the size of application on small systems
with size constraint issues. This option does not affect wide-char
formatted I/O functions. Some notes about the feature:
- 1) The non-wide-char formatted I/O functions only support the C89
- standard. The only exception is the configuration option provides
- limited support for long double. Internally, the nano formatted I/O
- functions use double so accuracy is only guaranteed to double
- precision.
+ 1) The non-wide-char formatted I/O functions support the C89
+ standard. When `--enable-newlib-nano-io-long-long' is configured and
+ `_printf_long_long' is linked, the printf-family functions additionally
+ support the "ll" integer length modifier. The scanf-family functions
+ continue to support C89 integer conversions. The only other exception
+ is the configuration option providing limited support for long double.
+ Internally, the nano formatted I/O functions use double so accuracy is
+ only guaranteed to double precision.
2) Floating-point support is split out of the formatted I/O code into
weak functions which are not linked by default. Programs that need
floating-point I/O support must explicitly request linking of one or
@@ -430,7 +438,13 @@ One feature can be enabled by specifying `--enable-FEATURE=yes' or
by default, but if the floating-point functions are not explicitly
linked in, this may result in undefined behavior for programs that
need floating-point I/O support.
- 3) Integer-only versions of the formatted I/O functions (the iprintf/
+ 3) Long long output conversion is similarly split into the
+ `_printf_long_long' function. With GCC-compatible compilers it is a
+ weak symbol, so programs that need long long output conversions with
+ printf-family functions must request it at link time with
+ `-u _printf_long_long'. Although long long format specifiers are
+ recognized, they are not rendered unless the helper is linked.
+ 4) Integer-only versions of the formatted I/O functions (the iprintf/
iscanf family) simply alias their regular counter-parts.
The affected functions are:
@@ -456,7 +470,7 @@ One feature can be enabled by specifying `--enable-FEATURE=yes' or
_viscanf_r _vfiscanf_r _vsiscanf_r
- 4) As mentioned, the option does not affect wide-char formatted I/O.
+ 5) As mentioned, the option does not affect wide-char formatted I/O.
The following configuration options are ignored for non-wide-char
formatted I/O functions, and can be thought of as disabled.
@@ -472,8 +486,7 @@ One feature can be enabled by specifying `--enable-FEATURE=yes' or
specifiers will not be recognized or handled, and the -u option
will not work either.
- 5) As a rule, no features from outside of C89 standard will be
- considered in this implementation.
+ 6) Other features from outside of C89 standard are not supported.
Disabled by default.
@@ -279,6 +279,17 @@ AC_ARG_ENABLE(newlib_nano_formatted_io,
esac],[newlib_nano_formatted_io=no])
AM_CONDITIONAL(NEWLIB_NANO_FORMATTED_IO, test x$newlib_nano_formatted_io = xyes)
+dnl Support --enable-newlib-nano-io-long-long
+AC_ARG_ENABLE(newlib-nano-io-long-long,
+[ --enable-newlib-nano-io-long-long
+ enable long long output in nano printf],
+[case "${enableval}" in
+ yes) newlib_nano_io_long_long=yes ;;
+ no) newlib_nano_io_long_long=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for newlib-nano-io-long-long) ;;
+ esac],[newlib_nano_io_long_long=no])
+AM_CONDITIONAL(NEWLIB_NANO_IO_LONG_LONG, test x$newlib_nano_io_long_long = xyes)
+
dnl Support --enable-retargetable-locking
dnl This option is also read in libc/configure.in. It is repeated
dnl here so that it shows up in the help text.
@@ -535,6 +546,10 @@ if test "${newlib_nano_formatted_io}" = "yes"; then
AC_DEFINE(_NANO_FORMATTED_IO, 1, [Define if small footprint nano-formatted-IO implementation used.])
fi
+if test "${newlib_nano_io_long_long}" = "yes"; then
+ AC_DEFINE(_WANT_NANO_IO_LONG_LONG, 1, [Define to enable long long output in nano printf.])
+fi
+
if test "${newlib_retargetable_locking}" = "yes"; then
AC_DEFINE(_RETARGETABLE_LOCKING, 1, [Define if using retargetable functions for default lock routines.])
fi
@@ -10,6 +10,9 @@ libc_a_SOURCES += \
%D%/nano-vfscanf.c \
%D%/nano-vfscanf_i.c \
%D%/nano-vfscanf_float.c
+if NEWLIB_NANO_IO_LONG_LONG
+libc_a_SOURCES += %D%/nano-vfprintf_ll.c
+endif
else
libc_a_SOURCES += \
%D%/fiprintf.c \
@@ -611,6 +611,16 @@ _VFPRINTF_R (struct _reent *data,
prt_data.flags |= (SHORTINT << (cp - flag_chars));
fmt++;
}
+#ifndef _NO_LONGLONG
+ /* The generic parser above consumes the first 'l'. A second one
+ selects a distinct long long argument and must also be consumed. */
+ if ((prt_data.flags & LONGINT) && *fmt == 'l')
+ {
+ prt_data.flags &= ~LONGINT;
+ prt_data.flags |= QUADINT;
+ fmt++;
+ }
+#endif
/* The conversion specifiers. */
prt_data.code = *fmt++;
@@ -633,6 +643,27 @@ _VFPRINTF_R (struct _reent *data,
n = _printf_float (data, &prt_data, fp, pfunc, &ap_copy);
}
else
+#endif
+#ifndef _NO_LONGLONG
+ /* Keep long long conversion in a separate archive member so ordinary
+ integer formatting does not pull in long long division support. */
+ if ((prt_data.flags & QUADINT)
+ && memchr ("diouxX", prt_data.code, 6))
+ {
+ /* Consume the correctly typed argument if _printf_long_long is not
+ linked, as is done for an unlinked _printf_float above. */
+ if (_printf_long_long == NULL)
+ {
+ if (prt_data.code == 'd' || prt_data.code == 'i')
+ GET_ARG (N, ap_copy, long long);
+ else
+ GET_ARG (N, ap_copy, unsigned long long);
+ n = 0;
+ }
+ else
+ n = _printf_long_long (data, &prt_data, fp, pfunc, &ap_copy);
+ }
+ else
#endif
n = _printf_i (data, &prt_data, fp, pfunc, &ap_copy);
@@ -201,6 +201,13 @@ number:
pdata->size = pdata->buf + BUF - cp;
break;
case 'n':
+#ifndef _NO_LONGLONG
+ /* %lln stores through a long long pointer; treating it as %ln would
+ use the wrong pointer type and update only part of the destination. */
+ if (pdata->flags & QUADINT)
+ *GET_ARG (N, *ap, long long *) = pdata->ret;
+ else
+#endif
if (pdata->flags & LONGINT)
*GET_ARG (N, *ap, long_ptr_t) = pdata->ret;
else if (pdata->flags & SHORTINT)
new file mode 100644
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2026 Morningstar Corporation.
+ * Copyright (c) 2012-2014 ARM Ltd
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the company may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ARM LTD ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL ARM LTD BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <newlib.h>
+
+#include <_ansi.h>
+#include <reent.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include "nano-vfprintf_local.h"
+
+#ifndef _NO_LONGLONG
+/* Decode and print a long long integer conversion. */
+int
+_printf_long_long (struct _reent *data,
+ struct _prt_data_t *pdata,
+ FILE *fp,
+ int (*pfunc)(struct _reent *, FILE *, const char *,
+ size_t len),
+ va_list *ap)
+{
+ unsigned long long value;
+ int base;
+ int n;
+ int realsz;
+ char *cp = pdata->buf + BUF;
+ const char *xdigs = "0123456789ABCDEF";
+
+ switch (pdata->code)
+ {
+ case 'd':
+ case 'i':
+ {
+ long long signed_value = GET_ARG (N, *ap, long long);
+
+ if (signed_value < 0)
+ {
+ value = -(unsigned long long) signed_value;
+ pdata->l_buf[0] = '-';
+ }
+ else
+ value = signed_value;
+ }
+ base = 10;
+ break;
+ case 'u':
+ value = GET_ARG (N, *ap, unsigned long long);
+ base = 10;
+ pdata->l_buf[0] = '\0';
+ break;
+ case 'o':
+ value = GET_ARG (N, *ap, unsigned long long);
+ base = 8;
+ pdata->l_buf[0] = '\0';
+ break;
+ case 'X':
+ value = GET_ARG (N, *ap, unsigned long long);
+ base = 16;
+ pdata->l_buf[0] = '\0';
+ pdata->l_buf[2] = 'X';
+ if (pdata->flags & ALT)
+ pdata->flags |= HEXPREFIX;
+ break;
+ default:
+ value = GET_ARG (N, *ap, unsigned long long);
+ base = 16;
+ pdata->l_buf[0] = '\0';
+ pdata->l_buf[2] = 'x';
+ xdigs = "0123456789abcdef";
+ if (pdata->flags & ALT)
+ pdata->flags |= HEXPREFIX;
+ break;
+ }
+
+ if (value == 0)
+ pdata->flags &= ~HEXPREFIX;
+
+ if ((pdata->dprec = pdata->prec) >= 0)
+ pdata->flags &= ~ZEROPAD;
+
+ if (value != 0 || pdata->prec != 0)
+ {
+ do
+ {
+ *--cp = xdigs[value % base];
+ value /= base;
+ }
+ while (value);
+ }
+
+ pdata->size = pdata->buf + BUF - cp;
+ if (base == 8 && (pdata->flags & ALT) && pdata->prec <= pdata->size
+ && (pdata->size == 0 || *cp != '0'))
+ {
+ *--cp = '0';
+ pdata->size++;
+ }
+
+ n = _printf_common (data, pdata, &realsz, fp, pfunc);
+ if (n == -1)
+ goto error;
+
+ PRINT (cp, pdata->size);
+ if (pdata->flags & LADJUST)
+ PAD (pdata->width - realsz, pdata->blank);
+
+ return (pdata->width > realsz ? pdata->width : realsz);
+error:
+ return -1;
+}
+#endif
@@ -74,6 +74,9 @@
#define _NO_LONGDBL
#define _NO_LONGLONG
+#ifdef _WANT_NANO_IO_LONG_LONG
+# undef _NO_LONGLONG
+#endif
#define _PRINTF_FLOAT_TYPE double
@@ -135,10 +138,13 @@ typedef short * short_ptr_t;
#define SHORTINT 0x040 /* Short integer. */
#define LONGINT 0x080 /* Long integer. */
#define LONGDBL 0x100 /* Long double. */
-/* ifdef _NO_LONGLONG, make QUADINT equivalent to LONGINT, so
- that %lld behaves the same as %ld, not as %d, as expected if:
- sizeof (long long) = sizeof long > sizeof int. */
-#define QUADINT LONGINT
+/* The otherwise unused 0x200 bit keeps long long distinct when support is
+ configured. */
+#ifndef _NO_LONGLONG
+# define QUADINT 0x200 /* Quad integer. */
+#else
+# define QUADINT LONGINT
+#endif
#define FPT 0x400 /* Floating point number. */
/* Define as 0, to make SARG and UARG occupy fewer instructions. */
# define CHARINT 0
@@ -222,6 +228,19 @@ _printf_i (struct _reent *data, struct _prt_data_t *pdata, FILE *fp,
int (*pfunc)(struct _reent *, FILE *, const char *, size_t len),
va_list *ap);
+#ifndef _NO_LONGLONG
+/* Make _printf_long_long weak with GCC-compatible compilers so ordinary
+ integer formatting does not pull long long conversion routines into
+ size-constrained applications. */
+extern int
+_printf_long_long (struct _reent *data,
+ struct _prt_data_t *pdata,
+ FILE *fp,
+ int (*pfunc)(struct _reent *, FILE *,
+ const char *, size_t len),
+ va_list *ap) _ATTRIBUTE((__weak__));
+#endif
+
/* Make _printf_float weak symbol, so it won't be linked in if target program
does not need it. */
extern int
new file mode 100644
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2026 Morningstar Corporation.
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * is freely granted, provided that this notice is preserved.
+ */
+
+#include <newlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "check.h"
+
+int
+main (void)
+{
+#if defined (_NANO_FORMATTED_IO) \
+ && defined (_WANT_NANO_IO_LONG_LONG) \
+ && defined (__GNUC__)
+ static const char expected[] = "before||after";
+ char actual[sizeof ("before|-1|after")];
+ int ret;
+
+ /* The weak helper is deliberately not linked by this test. */
+ ret = snprintf (actual, sizeof (actual), "%s|%lld|%s",
+ "before", -1LL, "after");
+ CHECK (ret == (int) strlen (expected));
+ CHECK (strcmp (actual, expected) == 0);
+#endif
+
+ exit (0);
+}
new file mode 100644
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2026 Morningstar Corporation.
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * is freely granted, provided that this notice is preserved.
+ */
+
+#include <newlib.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "check.h"
+
+#if defined (_NANO_FORMATTED_IO) && defined (_WANT_NANO_IO_LONG_LONG)
+struct _reent;
+struct _prt_data_t;
+extern int
+_printf_long_long (struct _reent *, struct _prt_data_t *, FILE *,
+ int (*)(struct _reent *, FILE *, const char *, size_t),
+ va_list *);
+int (*const force_printf_long_long)
+ (struct _reent *, struct _prt_data_t *, FILE *,
+ int (*)(struct _reent *, FILE *, const char *, size_t), va_list *)
+ = _printf_long_long;
+#endif
+
+int
+main (void)
+{
+#if defined (_NANO_FORMATTED_IO) && defined (_WANT_NANO_IO_LONG_LONG)
+ static const char expected[] =
+ "-9223372036854775807|18446744073709551615|ffffffffffffffff|"
+ "FEDCBA9876543210|1777777777777777777777|0x2a|"
+ "00000000000000000042|0018446744073709551615|-123|1234567890";
+ char actual[sizeof (expected)];
+ char epoch_seconds[sizeof ("2208988800")];
+ char truncated[5];
+ long long count = -1;
+ struct tm epoch_2040 = { 0 };
+ volatile size_t truncated_size = sizeof (truncated);
+ size_t time_len;
+ int ret;
+
+ ret = snprintf (actual, sizeof (actual),
+ "%lld|%llu|%llx|%llX|%llo|%#llx|"
+ "%020llu|%.22llu|%d|%lu%lln",
+ -9223372036854775807LL, 18446744073709551615ULL,
+ 18446744073709551615ULL, 0xfedcba9876543210ULL,
+ 18446744073709551615ULL, 42ULL, 42ULL,
+ 18446744073709551615ULL, -123, 1234567890UL, &count);
+ CHECK (ret == (int) strlen (expected));
+ CHECK (count == ret);
+ CHECK (strcmp (actual, expected) == 0);
+
+ ret = snprintf (truncated, truncated_size, "%llu",
+ 18446744073709551615ULL);
+ CHECK (ret == 20);
+ CHECK (strcmp (truncated, "1844") == 0);
+
+ ret = snprintf (actual, sizeof (actual), "%#llo", 0ULL);
+ CHECK (ret == 1);
+ CHECK (strcmp (actual, "0") == 0);
+ ret = snprintf (actual, sizeof (actual), "%#.0llo", 0ULL);
+ CHECK (ret == 1);
+ CHECK (strcmp (actual, "0") == 0);
+
+ ret = snprintf (actual, sizeof (actual), "%+lld|% lld|%-5llu|%#llX",
+ 7LL, 7LL, 8ULL, 42ULL);
+ CHECK (ret == 16);
+ CHECK (strcmp (actual, "+7| 7|8 |0X2A") == 0);
+
+ ret = snprintf (actual, sizeof (actual), "%lli", -123LL);
+ CHECK (ret == 4);
+ CHECK (strcmp (actual, "-123") == 0);
+
+# if defined (INT64_MAX) && defined (UINT64_MAX) \
+ && defined (PRId64) && defined (PRIu64) && defined (PRIx64)
+ ret = snprintf (actual, sizeof (actual),
+ "%" PRId64 "|%" PRIu64 "|%" PRIx64,
+ INT64_MIN, UINT64_MAX, UINT64_MAX);
+ CHECK (ret == 58);
+ CHECK (strcmp (actual,
+ "-9223372036854775808|18446744073709551615|ffffffffffffffff")
+ == 0);
+# endif
+
+ /* Newlib's strftime %s conversion formats its result with %lld. */
+ epoch_2040.tm_year = 140;
+ epoch_2040.tm_mday = 1;
+ epoch_2040.tm_isdst = -1;
+ time_len = strftime (epoch_seconds, sizeof (epoch_seconds), "%s",
+ &epoch_2040);
+ CHECK (time_len == 10);
+ CHECK (strcmp (epoch_seconds, "2208988800") == 0);
+
+# if LLONG_MAX == 9223372036854775807LL \
+ && LLONG_MIN == (-9223372036854775807LL - 1LL)
+ ret = snprintf (actual, sizeof (actual), "%lld", LLONG_MIN);
+ CHECK (ret == 20);
+ CHECK (strcmp (actual, "-9223372036854775808") == 0);
+# endif
+#endif
+
+ exit (0);
+}