Skip to content
This repository was archived by the owner on Feb 8, 2024. It is now read-only.

Fix vararg ABI #14

Merged
merged 5 commits into from
Feb 24, 2015
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
349 changes: 157 additions & 192 deletions src/core/stdc/stdarg.d
Original file line number Diff line number Diff line change
@@ -20,8 +20,6 @@ version ( PPC64 ) version = AnyPPC;

version( X86_64 )
{
version( LDC ) version = LDC_X86_64;

// Determine if type is a vector type
template isVectorType(T)
{
@@ -295,7 +293,161 @@ version( X86_64 )
}
}

version( X86 )
version( LDC )
{
// FIXME: This isn't actually tested at all for ARM.

version( X86_64 )
{
version( Win64 ) {}
else version = SystemV_AMD64;
}

// Type va_list:
// On most platforms, really struct va_list { void* ptr; },
// but for compatibility with x86-style code that uses char*,
// we just define it as the raw pointer.
// For System V AMD64 ABI, really __va_list[1], i.e., a 24-bytes
// struct passed by reference. We define va_list as a raw pointer
// (to the actual struct) for the byref semantics and allocate
// the struct in LDC's va_start and va_copy intrinsics.
alias char* va_list;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather actually make this __va_list* or something so that code that assumes x86-style variadics just doesn't compile instead of crashing horribly at runtime.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I see the point, but there's a problem with the hidden argptr. It is currently defined as Type::tvalist, i.e., char, see gen/target.cpp:81. If we change it to a hardcoded core.vararg.__va_list_ for System V, I guess the user must import core.vararg when implementing a variadic extern(D) function, even when not accessing _argptr at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I see the problem. We should probably fix this at some point by moving __va_list into object and properly initializing Type::tvalist, but that can wait for now.


pragma(LDC_va_start)
void va_start(T)(va_list ap, ref T);

private pragma(LDC_va_arg)
T va_arg_intrinsic(T)(va_list ap);

T va_arg(T)(ref va_list ap)
{
version( SystemV_AMD64 )
{
T arg;
va_arg(ap, arg);
return arg;
}
else version( Win64 )
{
// passed by reference if > 64 bits or of a size that is not a power of 2
static if (T.sizeof > size_t.sizeof || (T.sizeof & (T.sizeof - 1)) != 0)
T arg = **cast(T**)ap;
else
T arg = *cast(T*)ap;
ap += size_t.sizeof;
return arg;
}
else version( X86 )
{
T arg = *cast(T*)ap;
ap += (T.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
return arg;
}
else version( ARM )
{
T arg = *cast(T*)ap;
ap += (T.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
return arg;
}
else
return va_arg_intrinsic!T(ap);
}

void va_arg(T)(ref va_list ap, ref T parmn)
{
version( SystemV_AMD64 )
{
va_arg_x86_64(cast(__va_list*)ap, parmn);
}
else version( Win64 )
{
static if (T.sizeof > size_t.sizeof || (T.sizeof & (T.sizeof - 1)) != 0)
parmn = **cast(T**)ap;
else
parmn = *cast(T*)ap;
ap += size_t.sizeof;
}
else version( X86 )
{
parmn = *cast(T*)ap;
ap += (T.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
}
else version( ARM )
{
parmn = *cast(T*)ap;
ap += (T.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
}
else
parmn = va_arg!T(ap);
}

void va_arg()(ref va_list ap, TypeInfo ti, void* parmn)
{
version( SystemV_AMD64 )
{
va_arg_x86_64(cast(__va_list*)ap, ti, parmn);
}
else
{
auto tsize = ti.tsize;

version( X86 )
{
// Wait until everyone updates to get TypeInfo.talign
//auto talign = ti.talign;
//auto p = cast(va_list) ((cast(size_t)ap + talign - 1) & ~(talign - 1));
auto p = ap;
ap = p + ((tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1));
}
else version( Win64 )
{
auto p = (tsize > size_t.sizeof || (tsize & (tsize - 1)) != 0) ? *cast(char**)ap : ap;
ap += size_t.sizeof;
}
else version( ARM )
{
// Wait until everyone updates to get TypeInfo.talign
//auto talign = ti.talign;
//auto p = cast(va_list) ((cast(size_t)ap + talign - 1) & ~(talign - 1));
auto p = ap;
ap = p + ((tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1));
}
else version( AnyPPC )
{
/*
* The rules are described in the 64bit PowerPC ELF ABI Supplement 1.9,
* available here:
* http://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi-1.9.html#PARAM-PASS
*/

// This works for all types because only the rules for non-floating,
// non-vector types are used.
auto p = (tsize < size_t.sizeof ? ap + (size_t.sizeof - tsize) : ap);
ap += (tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
}
else version( MIPS64 )
{
// This works for all types because only the rules for non-floating,
// non-vector types are used.
auto p = (tsize < size_t.sizeof ? ap + (size_t.sizeof - tsize) : ap);
ap += (tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
}
else
{
static assert(false, "Unsupported platform");
}

parmn[0..tsize] = (cast(void*)p)[0..tsize];
}
}

pragma(LDC_va_end)
void va_end(va_list ap);

pragma(LDC_va_copy)
void va_copy(out va_list dest, va_list src);
}
else version( X86 )
{
/*********************
* The argument pointer type.
@@ -307,17 +459,9 @@ version( X86 )
* For 32 bit code, parmn should be the last named parameter.
* For 64 bit code, parmn should be __va_argsave.
*/
version(LDC)
{
pragma(LDC_va_start)
void va_start(T)(va_list ap, ref T);
}
else
void va_start(T)(out va_list ap, ref T parmn)
{
void va_start(T)(out va_list ap, ref T parmn)
{
ap = cast(va_list)( cast(void*) &parmn + ( ( T.sizeof + int.sizeof - 1 ) & ~( int.sizeof - 1 ) ) );
}
ap = cast(va_list)( cast(void*) &parmn + ( ( T.sizeof + int.sizeof - 1 ) & ~( int.sizeof - 1 ) ) );
}

/************
@@ -368,101 +512,6 @@ version( X86 )
dest = src;
}
}
else version( ARM )
{
// FIXME: This isn't actually tested at all.
// Really struct va_list { void* ptr; }, but for compatibility with
// x86-style code that uses char*, we just define it as the raw pointer.
alias va_list = char*;

version(LDC)
{
pragma(LDC_va_start)
void va_start(T)(va_list ap, ref T);
}
else static assert("Unsupported platform.");

/**
* Retrieve and return the next value that is type T.
* This is the preferred version.
*/
void va_arg(T)(ref va_list ap, ref T parmn)
{
parmn = *cast(T*)ap;
ap = cast(va_list)(cast(void*)ap + ((T.sizeof + int.sizeof - 1) & ~(int.sizeof - 1)));
}

/**
* Retrieve and store through parmn the next value that is of TypeInfo ti.
* Used when the static type is not known.
*/
void va_arg()(ref va_list ap, TypeInfo ti, void* parmn)
{
// Wait until everyone updates to get TypeInfo.talign
//auto talign = ti.talign;
//auto p = cast(void*)(cast(size_t)ap + talign - 1) & ~(talign - 1);
auto p = *cast(void**) &ap;
auto tsize = ti.tsize;
*cast(void**) &ap += ((tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1));
parmn[0..tsize] = p[0..tsize];
}

/**
* End use of ap.
*/
void va_end(va_list ap)
{
}

void va_copy(out va_list dest, va_list src)
{
dest = src;
}
}
else version ( LDC_X86_64 )
{
// We absolutely need va_list to be something that causes the actual struct
// to be passed by reference for compatibility with C function declarations.
// Otherwise, e.g. "extern(C) int vprintf(const char*, va_list)" would fail
// horribly. Now, with va_list = __va_list*, we can't properly implement
// va_copy without cheating, as it needs to save the struct contents
// somewhere (offset_regs, offset_fpregs), copying a pointer does not help.
//
// Currently, we just special-case va_start/va_copy when lowering the
// pragmas and make them allocate the struct on the caller's stack. This
// gets us 99% there for the common use cases. Eventually, the whole mess
// will have to be cleaned up (also in DMD), probably by making va_list a
// magic built-in type that on x86_64 decays to a reference like the actual
// definition in C (one-element array), or by giving va_copy a different
// signature.
alias va_list = __va_list*;

pragma(LDC_va_start)
void va_start(T)(out va_list ap, ref T);

T va_arg(T)(va_list ap)
{
T a;
va_arg(ap, a);
return a;
}

void va_arg(T)(va_list apx, ref T parmn)
{
va_arg_x86_64(apx, parmn);
}

void va_arg()(va_list apx, TypeInfo ti, void* parmn)
{
va_arg_x86_64(apx, ti, parmn);
}

pragma(LDC_va_end)
void va_end(va_list ap);

pragma(LDC_va_copy)
void va_copy(out va_list dest, va_list src);
}
else version (Windows) // Win64
{ /* Win64 is characterized by all arguments fitting into a register size.
* Smaller ones are padded out to register size, and larger ones are passed by
@@ -584,90 +633,6 @@ else version ( X86_64 )
dest = src;
}
}
else version ( AnyPPC )
{
version ( LDC )
{
/*
* The rules are described in the 64bit PowerPC ELF ABI Supplement 1.9,
* available here:
* http://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi-1.9.html#PARAM-PASS
*/

alias void *va_list;

pragma(LDC_va_start)
void va_start(T)(va_list ap, ref T);

private pragma(LDC_va_arg)
T va_arg_impl(T)(va_list ap);

T va_arg(T)(ref va_list ap)
{
return va_arg_impl!T(ap);
}

void va_arg(T)(ref va_list ap, ref T parmn)
{
parmn = va_arg!T(ap);
}

void va_arg()(ref va_list ap, TypeInfo ti, void* parmn)
{
// This works for all types because only the rules for non-floating,
// non-vector types are used.
auto tsize = ti.tsize();
auto p = tsize < size_t.sizeof ? cast(void*)(cast(void*)ap + (size_t.sizeof - tsize)) : ap;
ap = cast(va_list)(cast(void*)ap + ((tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1)));
parmn[0..tsize] = p[0..tsize];
}

pragma(LDC_va_end)
void va_end(va_list ap);

pragma(LDC_va_copy)
void va_copy(out va_list dest, va_list src);
}
}
else version ( MIPS64 )
{
version ( LDC )
{
alias void *va_list;

pragma(LDC_va_start)
void va_start(T)(va_list ap, ref T);

private pragma(LDC_va_arg)
T va_arg_impl(T)(va_list ap);

T va_arg(T)(ref va_list ap)
{
return va_arg_impl!T(ap);
}

void va_arg(T)(ref va_list ap, ref T parmn)
{
parmn = va_arg!T(ap);
}

void va_arg()(ref va_list ap, TypeInfo ti, void* parmn)
{
// This works for all types because only the rules for non-floating,
// non-vector types are used.
auto tsize = ti.tsize();
auto p = tsize < size_t.sizeof ? cast(void*)(cast(void*)ap + (size_t.sizeof - tsize)) : ap;
ap = cast(va_list)(cast(void*)ap + ((tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1)));
parmn[0..tsize] = p[0..tsize];
}

pragma(LDC_va_end)
void va_end(va_list ap);

pragma(LDC_va_copy)
void va_copy(out va_list dest, va_list src);
}
}
else
{
static assert(false, "Unsupported platform");
142 changes: 1 addition & 141 deletions src/core/vararg.d
Original file line number Diff line number Diff line change
@@ -16,144 +16,4 @@
*/
module core.vararg;

version( LDC )
{
/**
* The base vararg list type.
*/
alias va_list = char*;

/**
* This function initializes the supplied argument pointer for subsequent
* use by va_arg and va_end.
*
* Params:
* ap = The argument pointer to initialize.
* parmn = The identifier of the rightmost parameter in the function
* parameter list.
*/
pragma(LDC_va_start)
void va_start(T)(va_list ap, ref T);

/**
* This function returns the next argument in the sequence referenced by
* the supplied argument pointer. The argument pointer will be adjusted
* to point to the next arggument in the sequence.
*
* Params:
* ap = The argument pointer.
*
* Returns:
* The next argument in the sequence. The result is undefined if ap
* does not point to a valid argument.
*/
T va_arg(T)( ref va_list ap )
{
T arg = *cast(T*) ap;
ap = cast(va_list)( cast(void*) ap + ( ( T.sizeof + size_t.sizeof - 1 ) & ~( size_t.sizeof - 1 ) ) );
return arg;
}

/**
* This function cleans up any resources allocated by va_start. It is
* currently a no-op and exists mostly for syntax compatibility with
* the variadric argument functions for C.
*
* Params:
* ap = The argument pointer.
*/
void va_end( va_list ap )
{
}

/**
* This function copied the argument pointer src to dst.
*
* Params:
* src = The source pointer.
* dst = The destination pointer.
*/
void va_copy( out va_list dst, va_list src )
{
dst = src;
}

}

else version( X86 )
{
/**
* The base vararg list type.
*/
alias void* va_list;

/**
* This function initializes the supplied argument pointer for subsequent
* use by va_arg and va_end.
*
* Params:
* ap = The argument pointer to initialize.
* paramn = The identifier of the rightmost parameter in the function
* parameter list.
*/
version(LDC)
{
pragma(LDC_va_start)
void va_start(T)(va_list ap, ref T);
}
else
{
void va_start(T)( out va_list ap, ref T parmn )
{
ap = cast(va_list)( cast(void*) &parmn + ( ( T.sizeof + int.sizeof - 1 ) & ~( int.sizeof - 1 ) ) );
}
}

/**
* This function returns the next argument in the sequence referenced by
* the supplied argument pointer. The argument pointer will be adjusted
* to point to the next arggument in the sequence.
*
* Params:
* ap = The argument pointer.
*
* Returns:
* The next argument in the sequence. The result is undefined if ap
* does not point to a valid argument.
*/
T va_arg(T)( ref va_list ap )
{
T arg = *cast(T*) ap;
ap = cast(va_list)( cast(void*) ap + ( ( T.sizeof + int.sizeof - 1 ) & ~( int.sizeof - 1 ) ) );
return arg;
}

/**
* This function cleans up any resources allocated by va_start. It is
* currently a no-op and exists mostly for syntax compatibility with
* the variadric argument functions for C.
*
* Params:
* ap = The argument pointer.
*/
void va_end( va_list ap )
{
}

/**
* This function copied the argument pointer src to dst.
*
* Params:
* src = The source pointer.
* dst = The destination pointer.
*/
void va_copy( out va_list dst, va_list src )
{
dst = src;
}
}
else
{
public import core.stdc.stdarg;
}

public import core.stdc.stdarg;