#!/usr/bin/env python
#
# Copyright 2011 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#

from xml.dom import minidom

HEADER_TEMPL = """\
/*this file is auto_generated by volk_register.py*/

#include <volk/volk_cpu.h>
#include <volk/volk_config_fixed.h>

struct VOLK_CPU volk_cpu;

#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64)
#  define VOLK_CPU_x86
#endif

#if defined(VOLK_CPU_x86)

//implement get cpuid for gcc compilers using a copy of cpuid.h
#if defined(__GNUC__)
#include <gcc_x86_cpuid.h>
#define cpuid_x86(op, r) __get_cpuid(op, (unsigned int *)r+0, (unsigned int *)r+1, (unsigned int *)r+2, (unsigned int *)r+3)

    /* Return Intel AVX extended CPU capabilities register.
     * This function will bomb on non-AVX-capable machines, so
     * check for AVX capability before executing.
     */
    #if __GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 4
    static inline unsigned long long _xgetbv(unsigned int index){
        unsigned int eax, edx;
        __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index));
        return ((unsigned long long)edx << 32) | eax;
    }
    #define __xgetbv() _xgetbv(0)
    #else
    #define __xgetbv() 0
    #endif

//implement get cpuid for MSVC compilers using __cpuid intrinsic
#elif defined(_MSC_VER)
#include <intrin.h>
#define cpuid_x86(op, r) __cpuid(r, op)

    #if defined(_XCR_XFEATURE_ENABLED_MASK)
    #define __xgetbv() _xgetbv(_XCR_XFEATURE_ENABLED_MASK)
    #else
    #define __xgetbv() 0
    #endif

#else
#error "A get cpuid for volk is not available on this compiler..."
#endif

static inline unsigned int cpuid_eax(unsigned int op) {
    int regs[4];
    cpuid_x86 (op, regs);
    return regs[0];
}

static inline unsigned int cpuid_ebx(unsigned int op) {
    int regs[4];
    cpuid_x86 (op, regs);
    return regs[1];
}

static inline unsigned int cpuid_ecx(unsigned int op) {
    int regs[4];
    cpuid_x86 (op, regs);
    return regs[2];
}

static inline unsigned int cpuid_edx(unsigned int op) {
    int regs[4];
    cpuid_x86 (op, regs);
    return regs[3];
}

static inline unsigned int xgetbv(void) {
    //check to make sure that xgetbv is enabled in OS
    int xgetbv_enabled = cpuid_ecx(1) >> 27 & 0x01;
    if(xgetbv_enabled == 0) return 0;
    return __xgetbv();
}
#endif

"""

def make_cpuid_c(dom) :
    tempstring = HEADER_TEMPL;

    for domarch in dom:
        if str(domarch.attributes["type"].value) == "x86":
            if "no_test" in domarch.attributes.keys():
                no_test = str(domarch.attributes["no_test"].value);
                if no_test == "true":
                    no_test = True;
                else:
                    no_test = False;
            else:
                no_test = False;
            arch = str(domarch.attributes["name"].value)
            op = domarch.getElementsByTagName("op")
            if op:
                op = str(op[0].firstChild.data)
            reg = domarch.getElementsByTagName("reg")
            if reg:
                reg = str(reg[0].firstChild.data)
            shift = domarch.getElementsByTagName("shift")
            if shift:
                shift = str(shift[0].firstChild.data)
            val = domarch.getElementsByTagName("val")
            if val:
                val = str(val[0].firstChild.data)
            check = domarch.getElementsByTagName("check")
            if check:
                check = str(check[0].firstChild.data)
            checkval = domarch.getElementsByTagName("checkval")
            if checkval:
                checkval = str(checkval[0].firstChild.data)

            if no_test:
                tempstring = tempstring + """\
int i_can_has_%s () {
#if defined(VOLK_CPU_x86)
    return 1;
#else
    return 0;
#endif
}

""" % (arch)

            elif op == "1":
                tempstring = tempstring + """\
int i_can_has_%s () {
#if defined(VOLK_CPU_x86)
    unsigned int e%sx = cpuid_e%sx (%s);
    int hwcap = (((e%sx >> %s) & 1) == %s);
""" % (arch, reg, reg, op, reg, shift, val)

                if check and checkval:
                    tempstring += """\
    if (hwcap == 0) return 0;
    hwcap &= (%s() == %s);
""" % (check, checkval)

                tempstring += """\
    return hwcap;
#else
    return 0;
#endif
}

"""

            elif op == "0x80000001":
                tempstring = tempstring + """\
int i_can_has_%s () {
#if defined(VOLK_CPU_x86)
    unsigned int extended_fct_count = cpuid_eax(0x80000000);
    if (extended_fct_count < 0x80000001)
        return %s^1;
    unsigned int extended_features = cpuid_e%sx (%s);
    return ((extended_features >> %s) & 1) == %s;
#else
    return 0;
#endif
}

""" % (arch, val, reg, op, shift, val)

        elif str(domarch.attributes["type"].value) == "powerpc":
            arch = str(domarch.attributes["name"].value);
            tempstring = tempstring + """\
int i_can_has_%s () {
#ifdef __PPC__
    return 1;
#else
    return 0;
#endif
}

""" % (arch)

        elif str(domarch.attributes["type"].value) == "arm":
            arch = str(domarch.attributes["name"].value);
            tempstring = tempstring + """\
#if defined(__arm__) && defined(__linux__)
#include <asm/hwcap.h>
#include <linux/auxvec.h>
#include <stdio.h>
#define LOOK_FOR_NEON
#endif

int i_can_has_%s () {
//it's linux-specific, but if you're compiling libvolk for NEON
//on Windows you have other problems

#ifdef LOOK_FOR_NEON
    FILE *auxvec_f;
    unsigned long auxvec[2];
    unsigned int found_neon = 0;
    auxvec_f = fopen("/proc/self/auxv", "rb");
    if(!auxvec_f) return 0;

    //so auxv is basically 32b of ID and 32b of value
    //so it goes like this
    while(!found_neon && auxvec_f) {
      fread(auxvec, sizeof(unsigned long), 2, auxvec_f);
      if((auxvec[0] == AT_HWCAP) && (auxvec[1] & HWCAP_NEON))
        found_neon = 1;
    }

    fclose(auxvec_f);
    return found_neon;

#else
    return 0;
#endif
}

""" % (arch)

        elif str(domarch.attributes["type"].value) == "all":
            arch = str(domarch.attributes["name"].value);
            tempstring = tempstring + """\
int i_can_has_%s () {
    return 1;
}

""" % (arch)
        else:
            arch = str(domarch.attributes["name"].value);
            tempstring = tempstring + """\
int i_can_has_%s () {
    return 0;
}

""" % (arch)

    tempstring = tempstring + "void volk_cpu_init() {\n";
    for domarch in dom:
        arch = str(domarch.attributes["name"].value);
        tempstring = tempstring + "    volk_cpu.has_" + arch + " = &i_can_has_" + arch + ";\n"
    tempstring = tempstring + "}\n\n"

    tempstring = tempstring + "unsigned int volk_get_lvarch() {\n";
    tempstring = tempstring + "    unsigned int retval = 0;\n"
    tempstring = tempstring + "    volk_cpu_init();\n"
    for domarch in dom:
        arch = str(domarch.attributes["name"].value);
        tempstring = tempstring + "    retval += volk_cpu.has_" + arch + "() << LV_" + arch.swapcase() + ";\n"
    tempstring = tempstring + "    return retval;\n"
    tempstring = tempstring + "}\n\n"

    return tempstring;