Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date: Fri, 15 Aug 2014 09:44:34 -0400
From: Rich Felker <dalias@...c.org>
To: u-igbb@...ey.se
Cc: musl@...ts.openwall.com
Subject: Re: va_list (was: compiling musl on x86_64 linux with
 pcc)

On Fri, Aug 15, 2014 at 12:49:36PM +0200, u-igbb@...ey.se wrote:
> On Thu, Aug 14, 2014 at 10:47:02AM -0400, Rich Felker wrote:
> > > > > That's a pity. Wonder whether it would cost too much to make
> > > > > __builtin_va_list non-mandatory?
> > > > 
> > > > How else would you do it? There's no real alternative.
> 
> To be able to mix object files and/or libraries compiled with different
> compilers apparently there must be a standard ABI.

C code has absolutely no access to the ABI. There is no way to write,
in C, "offset X from the stack pointer", and even if there were (e.g.
via inline asm), there is no way to know the current offset of the
stack pointer relative to what it was at function entry.

> This seems also to be reflected in
> 
>  https://stackoverflow.com/questions/4958384/what-is-the-format-of-the-x86-64-va-list-structure
> 
> and (referred from there)
> 
>  http://www.x86-64.org/documentation/abi.pdf
> 
> I may be missing something but it looks like this ABI can not be an opaque
> fully "compiler's internal business". Compilers may implement as much
> optimizations as they wish but they must be able to produce interoperable
> object files, don't they?

Yes, but this all pertains to calls between (external or potentially
externally visible via function pointer) functions. It has nothing to
do with what happens inside the function once its called.

I'll try to explain with the (wrong) legacy stdarg.h macro definitions
for i386 (much simpler) as an example. In

void foo(int a, ...)
{
	va_list ap;
	va_start(ap, a);
	int b = va_arg(ap, int);
	...
}

the legacy macros are taking the address of a, adding 4 to it, and
using the result to access the first variadic argument which is then
stored in b. However, there is utterly no relationship between the
address of a and the location where the variadic argument is stored!

There is a relationship between the addresses where the _value_ which
is stored in the _local, automatic variable_ a is passed on the stack,
and where the value for the first variadic argument is passed. But
there is no "is" relationship between the object a and the storage
where this value is passed. E.g. in the following function:

int bar(int n)
{
	int n0 = n;
	...
	/* maybe use &n ? */
	...
	return n-n0;
}

a very reasonable compiler would choose to use the original storage on
the stack where the argument was passed as n0, and create new storage
for n, possibly even keeping it in a register and never creating space
for it on the stack unless &n is actually used and possibly
dereferenced.

Other compilers might _always_ create new storage for arguments just
like other automatic objects, and simply use the values passed on the
stack to initialize them.

> I would appreciate to be enlightened on this matter, what is the obstacle
> to adding a compiler-independent definition of va_list (or otherwise
> possibly even using the compiler-specific non-builtin definition, like
> the one of tcc)?

So there is fundamentally no way to write a "portable" stdarg.h or
even a "compiler independent" one for a specific ABI, because it has
nothing to do with the ABI. The correct implementation has to do with
how the compiler generates code that's entirely _internal_ to the
function being compiled.

Further, variadic functions need not be external, externally callable,
or even existant as independent functions. If the above example foo is
inlined, the object a need not even have an address; if it does, it
would necessarily be created at some new location in the caller's
stack frame, not as part of a list of incoming argument values on the
stack.

> This of course does not have to be used when the presumably more efficient
> builtin is available.

It's not a matter of efficiency but correctness. The __builtin version
is compatible with any compiler that implements it, and any non-crap
(e.g. supporting inline, and making any nontrivial optimizations)
compiler must implement it or something equivalent with a gratuitously
different name. The legacy definition for i386 only works for utterly
idiotic compilers like tcc (and maybe gcc 2.x) that use a fixed
mechanical transformation to machine code for the _internals_ of the
variadic function being compiled.

Rich

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.