
/**
 * C's &lt;stdarg.h&gt;
 * This is for use with extern(C) variable argument lists.
 * Authors: Hauke Duden and Walter Bright, Digital Mars, http://www.digitalmars.com
 * License: Public Domain
 * Source: $(PHOBOSSRC std/c/_stdarg.d)
 * Macros:
 *      WIKI=Phobos/StdCStdarg
 */

/* NOTE: This file has been patched from the original DMD distribution to
   work with the GDC compiler.

   Modified by David Friedman, September 2004
*/

module std.c.stdarg;

version (GNU)
{
    private import gcc.builtins;
    alias __builtin_va_list va_list;
    alias __builtin_va_end va_end;
    alias __builtin_va_copy va_copy;

    version( X86_64 )
    {   // TODO: implement this?
        alias __builtin_va_list __va_argsave_t;
    }

    // The va_start and va_arg template functions are magically
    // handled by the compiler.
    void va_start(T)(out va_list ap, inout T parmn);
    T va_arg(T)(inout va_list ap);
}
else version (X86)
{
    /*********************
     * The argument pointer type.
     */
    alias void* va_list;

    /**********
     * Initialize ap.
     * For 32 bit code, parmn should be the last named parameter.
     * For 64 bit code, parmn should be __va_argsave.
     */
    void va_start(T)(out va_list ap, inout T parmn)
    {
        ap = cast(va_list)(cast(void*)&parmn + ((T.sizeof + int.sizeof - 1) & ~(int.sizeof - 1)));
    }

    /************
     * Retrieve and return the next value that is type T.
     * Should use the other va_arg instead, as this won't work for 64 bit code.
     */
    T va_arg(T)(inout 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;
    }

    /************
     * Retrieve and return the next value that is type T.
     * This is the preferred version.
     */
    void va_arg(T)(inout va_list ap, inout 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()(inout 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 = ap;
        auto tsize = ti.tsize();
        ap = cast(void*)(cast(size_t)p + ((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 (X86_64)
{
    // Layout of this struct must match __gnuc_va_list for C ABI compatibility
    struct __va_list
    {
        uint offset_regs = 6 * 8;            // no regs
        uint offset_fpregs = 6 * 8 + 8 * 16; // no fp regs
        void* stack_args;
        void* reg_args;
    }

    struct __va_argsave_t
    {
        size_t[6] regs;   // RDI,RSI,RDX,RCX,R8,R9
        real[8] fpregs;   // XMM0..XMM7
        __va_list va;
    }

    /*
     * Making it an array of 1 causes va_list to be passed as a pointer in
     * function argument lists
     */
    alias void* va_list;

    void va_start(T)(out va_list ap, inout T parmn)
    {
        ap = &parmn.va;
    }

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

    void va_arg(T)(va_list apx, inout T parmn)
    {
        __va_list* ap = cast(__va_list*)apx;
        static if (is(T U == __argTypes))
        {
            static if (U.length == 0 || T.sizeof > 16 || U[0].sizeof > 8)
            {   // Always passed in memory
                // The arg may have more strict alignment than the stack
                auto p = (cast(size_t)ap.stack_args + T.alignof - 1) & ~(T.alignof - 1);
                ap.stack_args = cast(void*)(p + ((T.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1)));
                parmn = *cast(T*)p;
            }
            else static if (U.length == 1)
            {   // Arg is passed in one register
                alias U[0] T1;
                static if (is(T1 == double) || is(T1 == float))
                {   // Passed in XMM register
                    if (ap.offset_fpregs < (6 * 8 + 16 * 8))
                    {
                        parmn = *cast(T*)(ap.reg_args + ap.offset_fpregs);
                        ap.offset_fpregs += 16;
                    }
                    else
                    {
                        parmn = *cast(T*)ap.stack_args;
                        ap.stack_args += (T1.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
                    }
                }
                else
                {   // Passed in regular register
                    if (ap.offset_regs < 6 * 8 && T.sizeof <= 8)
                    {
                        parmn = *cast(T*)(ap.reg_args + ap.offset_regs);
                        ap.offset_regs += 8;
                    }
                    else
                    {
                        auto p = (cast(size_t)ap.stack_args + T.alignof - 1) & ~(T.alignof - 1);
                        ap.stack_args = cast(void*)(p + ((T.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1)));
                        parmn = *cast(T*)p;
                    }
                }
            }
            else static if (U.length == 2)
            {   // Arg is passed in two registers
                alias U[0] T1;
                alias U[1] T2;

                static if (is(T1 == double) || is(T1 == float))
                {
                    if (ap.offset_fpregs < (6 * 8 + 16 * 8))
                    {
                        *cast(T1*)&parmn = *cast(T1*)(ap.reg_args + ap.offset_fpregs);
                        ap.offset_fpregs += 16;
                    }
                    else
                    {
                        *cast(T1*)&parmn = *cast(T1*)ap.stack_args;
                        ap.stack_args += (T1.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
                    }
                }
                else
                {
                    if (ap.offset_regs < 6 * 8 && T1.sizeof <= 8)
                    {
                        *cast(T1*)&parmn = *cast(T1*)(ap.reg_args + ap.offset_regs);
                        ap.offset_regs += 8;
                    }
                    else
                    {
                        *cast(T1*)&parmn = *cast(T1*)ap.stack_args;
                        ap.stack_args += 8;
                    }
                }

                auto p = cast(void*)&parmn + 8;
                static if (is(T2 == double) || is(T2 == float))
                {
                    if (ap.offset_fpregs < (6 * 8 + 16 * 8))
                    {
                        *cast(T2*)p = *cast(T2*)(ap.reg_args + ap.offset_fpregs);
                        ap.offset_fpregs += 16;
                    }
                    else
                    {
                        *cast(T2*)p = *cast(T2*)ap.stack_args;
                        ap.stack_args += (T2.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
                    }
                }
                else
                {
                    void* a = void;
                    if (ap.offset_regs < 6 * 8 && T2.sizeof <= 8)
                    {
                        a = ap.reg_args + ap.offset_regs;
                        ap.offset_regs += 8;
                    }
                    else
                    {
                        a = ap.stack_args;
                        ap.stack_args += 8;
                    }
                    // Be careful not to go past the size of the actual argument
                    const sz2 = T.sizeof - 8;
                    p[0..sz2] = a[0..sz2];
                }
            }
            else
            {
                static assert(0);
            }
        }
        else
        {
            static assert(0, "not a valid argument type for va_arg");
        }
    }

    void va_arg()(va_list apx, TypeInfo ti, void* parmn)
    {
        __va_list* ap = cast(__va_list*)apx;
        TypeInfo arg1, arg2;
        if (!ti.argTypes(arg1, arg2))
        {
            if (arg1 && arg1.tsize() <= 8)
            {   // Arg is passed in one register
                auto tsize = arg1.tsize();
                void* p;
                auto s = arg1.toString();
                if (s == "double" || s == "float" || s == "idouble" || s == "ifloat")
                {   // Passed in XMM register
                    if (ap.offset_fpregs < (6 * 8 + 16 * 8))
                    {
                        p = ap.reg_args + ap.offset_fpregs;
                        ap.offset_fpregs += 16;
                    }
                    else
                    {
                        p = ap.stack_args;
                        ap.stack_args += (tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
                    }
                }
                else
                {   // Passed in regular register
                    if (ap.offset_regs < 6 * 8)
                    {
                        p = ap.reg_args + ap.offset_regs;
                        ap.offset_regs += 8;
                    }
                    else
                    {
                        p = ap.stack_args;
                        ap.stack_args += 8;
                    }
                }
                parmn[0..tsize] = p[0..tsize];

                if (arg2)
                {
                    parmn += 8;
                    tsize = arg2.tsize();
                    s = arg2.toString();
                    if (s == "double" || s == "float" || s == "idouble" || s == "ifloat")
                    {   // Passed in XMM register
                        if (ap.offset_fpregs < (6 * 8 + 16 * 8))
                        {
                            p = ap.reg_args + ap.offset_fpregs;
                            ap.offset_fpregs += 16;
                        }
                        else
                        {
                            p = ap.stack_args;
                            ap.stack_args += (tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
                        }
                    }
                    else
                    {   // Passed in regular register
                        if (ap.offset_regs < 6 * 8)
                        {
                            p = ap.reg_args + ap.offset_regs;
                            ap.offset_regs += 8;
                        }
                        else
                        {
                            p = ap.stack_args;
                            ap.stack_args += 8;
                        }
                    }
                    tsize = ti.tsize() - 8;
                    parmn[0..tsize] = p[0..tsize];
                }
            }
            else
            {   // Always passed in memory
                // The arg may have more strict alignment than the stack
                auto talign = ti.talign();
                auto tsize = ti.tsize();
                auto p = cast(void*)((cast(size_t)ap.stack_args + talign - 1) & ~(talign - 1));
                ap.stack_args = cast(void*)(cast(size_t)p + ((tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1)));
                parmn[0..tsize] = p[0..tsize];
            }
        }
        else
        {
            assert(0, "not a valid argument type for va_arg");
        }
    }

    void va_end(va_list ap)
    {
    }

    void va_copy(out va_list dest, va_list src)
    {
        dest = src;
    }
}
else
{
    static assert(0);
}
