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
Show file tree
Hide file tree
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
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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.
Expand All @@ -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 ) ) );
}

/************
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand Down
Loading