Override GCC's varargs argument promotion

Viewed 311

I'm working on a piece of code for an ARM processor running the Cortex-M4 architecture (has single precision floats but not double). The issue I'm running into is when using varargs and the compiler tries to promote my floats into doubles. Is there any way to turn this off? Or specify a different promotion strategy? I've looked in the GCC manuals but couldn't find anything. A simple example is trying to write your own printf...

void myprintf(const char *fmt, ...) {
  // .. parse fmt and come across %f

  double dv = va_arg(args, double); // Compiles but doesn't link

  float fv = va_arg(args, float); // Doesn't compile (warns about promotion)
}

UPDATE: As requested, the call command I'm looking to use is myprintf("%f", 1.0f);. The issue is that the Cortex-M4 doesn't support doubles, so when 1.0f is promoted to a double... well it can't. So reading it back using va_arg(args, double) will compile but won't link as the various __aeabi_ functions related to doubles don't exist. What I want to do is disable GCC's promotion of floats to doubles (if possible).

UPDATE: I have a current workaround that basically just accepts pointers to floats instead of floats. I'd still like to disable the promotion as this isn't a great solution.

1 Answers

Converting vararg float arguments to double is not a gcc quirk or add-on. It is required by the C standard. §6.5.2.3 (Function calls):

  1. If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. …
  2. If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

Gcc does provide a few options which are not compatible with the C standard, but the vast majority add behaviour only in cases where the standard fails to do so. A change of this magnitude would affect the standard calling convention, making the compiled code incompatible with other compiled code such as libraries, standard or otherwise. Gcc does not provide such an option. Other compilers might, but I don't know of any which do.

Note that it is not possible, within the constraints of the standard, to just makedouble a synonym for float if float is a 32-bit value. The minimum precision requirements in §5.2.4.2.2 effectively require that a double have at least a 32-bit mantissa in addition to the exponent and sign. That's a lot less precision than is provided by 64-bit IEEE-749 doubles, but it's obviously more than can be provided in a 32-bit value.

It would certainly be possible for a compiler to provide an option where doubles are represented in a less precise and computationally faster format, such as two floats where the mantissas don't overlap. (That is, the exponents differ by at least the width of the mantissa.) That representation, or something similar, was used by at least one legacy implementation IIRC, precisely because it allows single-precision hardware to be used for double-precision computation. It also makes casting a float to a double trivial; it's only necessary to make the low-order value 0.0. Alternatively, a compiler could use a 32-bit mantissa combined with a short exponent, which allows use of the integer ALU.

I don't believe GCC offers that alternative either, though.

Related