/*
 *
 * vcomp implementation
 *
 * Copyright 2011 Austin English
 * Copyright 2012 Dan Kegel
 * Copyright 2015-2016 Sebastian Lackner
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"
#include "wine/port.h"

#include <stdarg.h>
#include <assert.h>

#include "windef.h"
#include "winbase.h"
#include "winternl.h"
#include "wine/debug.h"
#include "wine/list.h"

WINE_DEFAULT_DEBUG_CHANNEL(vcomp);

typedef CRITICAL_SECTION *omp_lock_t;
typedef CRITICAL_SECTION *omp_nest_lock_t;

static struct list vcomp_idle_threads = LIST_INIT(vcomp_idle_threads);
static DWORD   vcomp_context_tls = TLS_OUT_OF_INDEXES;
static HMODULE vcomp_module;
static int     vcomp_max_threads;
static int     vcomp_num_threads;
static BOOL    vcomp_nested_fork = FALSE;

static RTL_CRITICAL_SECTION vcomp_section;
static RTL_CRITICAL_SECTION_DEBUG critsect_debug =
{
    0, 0, &vcomp_section,
    { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
      0, 0, { (DWORD_PTR)(__FILE__ ": vcomp_section") }
};
static RTL_CRITICAL_SECTION vcomp_section = { &critsect_debug, -1, 0, 0, 0, 0 };

#define VCOMP_DYNAMIC_FLAGS_STATIC      0x01
#define VCOMP_DYNAMIC_FLAGS_CHUNKED     0x02
#define VCOMP_DYNAMIC_FLAGS_GUIDED      0x03
#define VCOMP_DYNAMIC_FLAGS_INCREMENT   0x40

struct vcomp_thread_data
{
    struct vcomp_team_data  *team;
    struct vcomp_task_data  *task;
    int                     thread_num;
    BOOL                    parallel;
    int                     fork_threads;

    /* only used for concurrent tasks */
    struct list             entry;
    CONDITION_VARIABLE      cond;

    /* single */
    unsigned int            single;

    /* section */
    unsigned int            section;

    /* dynamic */
    unsigned int            dynamic;
    unsigned int            dynamic_type;
    unsigned int            dynamic_begin;
    unsigned int            dynamic_end;
};

struct vcomp_team_data
{
    CONDITION_VARIABLE      cond;
    int                     num_threads;
    int                     finished_threads;

    /* callback arguments */
    int                     nargs;
    void                    *wrapper;
    __ms_va_list            valist;

    /* barrier */
    unsigned int            barrier;
    int                     barrier_count;
};

struct vcomp_task_data
{
    /* single */
    unsigned int            single;

    /* section */
    unsigned int            section;
    int                     num_sections;
    int                     section_index;

    /* dynamic */
    unsigned int            dynamic;
    unsigned int            dynamic_first;
    unsigned int            dynamic_last;
    unsigned int            dynamic_iterations;
    int                     dynamic_step;
    unsigned int            dynamic_chunksize;
};

#if defined(__i386__)

extern void CDECL _vcomp_fork_call_wrapper(void *wrapper, int nargs, __ms_va_list args);
__ASM_GLOBAL_FUNC( _vcomp_fork_call_wrapper,
                   "pushl %ebp\n\t"
                   __ASM_CFI(".cfi_adjust_cfa_offset 4\n\t")
                   __ASM_CFI(".cfi_rel_offset %ebp,0\n\t")
                   "movl %esp,%ebp\n\t"
                   __ASM_CFI(".cfi_def_cfa_register %ebp\n\t")
                   "pushl %esi\n\t"
                   __ASM_CFI(".cfi_rel_offset %esi,-4\n\t")
                   "pushl %edi\n\t"
                   __ASM_CFI(".cfi_rel_offset %edi,-8\n\t")
                   "movl 12(%ebp),%edx\n\t"
                   "movl %esp,%edi\n\t"
                   "shll $2,%edx\n\t"
                   "jz 1f\n\t"
                   "subl %edx,%edi\n\t"
                   "andl $~15,%edi\n\t"
                   "movl %edi,%esp\n\t"
                   "movl 12(%ebp),%ecx\n\t"
                   "movl 16(%ebp),%esi\n\t"
                   "cld\n\t"
                   "rep; movsl\n"
                   "1:\tcall *8(%ebp)\n\t"
                   "leal -8(%ebp),%esp\n\t"
                   "popl %edi\n\t"
                   __ASM_CFI(".cfi_same_value %edi\n\t")
                   "popl %esi\n\t"
                   __ASM_CFI(".cfi_same_value %esi\n\t")
                   "popl %ebp\n\t"
                   __ASM_CFI(".cfi_def_cfa %esp,4\n\t")
                   __ASM_CFI(".cfi_same_value %ebp\n\t")
                   "ret" )

#elif defined(__x86_64__)

extern void CDECL _vcomp_fork_call_wrapper(void *wrapper, int nargs, __ms_va_list args);
__ASM_GLOBAL_FUNC( _vcomp_fork_call_wrapper,
                   "pushq %rbp\n\t"
                   __ASM_CFI(".cfi_adjust_cfa_offset 8\n\t")
                   __ASM_CFI(".cfi_rel_offset %rbp,0\n\t")
                   "movq %rsp,%rbp\n\t"
                   __ASM_CFI(".cfi_def_cfa_register %rbp\n\t")
                   "pushq %rsi\n\t"
                   __ASM_CFI(".cfi_rel_offset %rsi,-8\n\t")
                   "pushq %rdi\n\t"
                   __ASM_CFI(".cfi_rel_offset %rdi,-16\n\t")
                   "movq %rcx,%rax\n\t"
                   "movq $4,%rcx\n\t"
                   "cmp %rcx,%rdx\n\t"
                   "cmovgq %rdx,%rcx\n\t"
                   "leaq 0(,%rcx,8),%rdx\n\t"
                   "subq %rdx,%rsp\n\t"
                   "andq $~15,%rsp\n\t"
                   "movq %rsp,%rdi\n\t"
                   "movq %r8,%rsi\n\t"
                   "rep; movsq\n\t"
                   "movq 0(%rsp),%rcx\n\t"
                   "movq 8(%rsp),%rdx\n\t"
                   "movq 16(%rsp),%r8\n\t"
                   "movq 24(%rsp),%r9\n\t"
                   "callq *%rax\n\t"
                   "leaq -16(%rbp),%rsp\n\t"
                   "popq %rdi\n\t"
                   __ASM_CFI(".cfi_same_value %rdi\n\t")
                   "popq %rsi\n\t"
                   __ASM_CFI(".cfi_same_value %rsi\n\t")
                   __ASM_CFI(".cfi_def_cfa_register %rsp\n\t")
                   "popq %rbp\n\t"
                   __ASM_CFI(".cfi_adjust_cfa_offset -8\n\t")
                   __ASM_CFI(".cfi_same_value %rbp\n\t")
                   "ret")

#elif defined(__arm__)

extern void CDECL _vcomp_fork_call_wrapper(void *wrapper, int nargs, __ms_va_list args);
__ASM_GLOBAL_FUNC( _vcomp_fork_call_wrapper,
                   ".arm\n\t"
                   "push {r4, r5, LR}\n\t"
                   "mov r4, r0\n\t"
                   "mov r5, SP\n\t"
                   "lsl r3, r1, #2\n\t"
                   "cmp r3, #0\n\t"
                   "beq 5f\n\t"
                   "sub SP, SP, r3\n\t"
                   "tst r1, #1\n\t"
                   "subeq SP, SP, #4\n\t"
                   "1:\tsub r3, r3, #4\n\t"
                   "ldr r0, [r2, r3]\n\t"
                   "str r0, [SP, r3]\n\t"
                   "cmp r3, #0\n\t"
                   "bgt 1b\n\t"
                   "cmp r1, #1\n\t"
                   "bgt 2f\n\t"
                   "pop {r0}\n\t"
                   "b 5f\n\t"
                   "2:\tcmp r1, #2\n\t"
                   "bgt 3f\n\t"
                   "pop {r0-r1}\n\t"
                   "b 5f\n\t"
                   "3:\tcmp r1, #3\n\t"
                   "bgt 4f\n\t"
                   "pop {r0-r2}\n\t"
                   "b 5f\n\t"
                   "4:\tpop {r0-r3}\n\t"
                   "5:\tblx r4\n\t"
                   "mov SP, r5\n\t"
                   "pop {r4, r5, PC}" )

#elif defined(__aarch64__)

extern void CDECL _vcomp_fork_call_wrapper(void *wrapper, int nargs, __ms_va_list args);
__ASM_GLOBAL_FUNC( _vcomp_fork_call_wrapper,
                   "stp x29, x30, [SP,#-16]!\n\t"
                   "mov x29, SP\n\t"
                   "mov x9, x0\n\t"
                   "cbz w1, 2f\n\t"
                   "mov w10, w1\n\t"
                   "mov x11, x2\n\t"
                   "ldr w12, [x11, #24]\n\t"
                   "ldr x13, [x11, #8]\n\t"
                   "ldr x0, [x13, w12, sxtw]\n\t"
                   "add w12, w12, #8\n\t"
                   "ldr x1, [x13, w12, sxtw]\n\t"
                   "add w12, w12, #8\n\t"
                   "ldr x2, [x13, w12, sxtw]\n\t"
                   "add w12, w12, #8\n\t"
                   "ldr x3, [x13, w12, sxtw]\n\t"
                   "add w12, w12, #8\n\t"
                   "ldr x4, [x13, w12, sxtw]\n\t"
                   "add w12, w12, #8\n\t"
                   "ldr x5, [x13, w12, sxtw]\n\t"
                   "add w12, w12, #8\n\t"
                   "ldr x6, [x13, w12, sxtw]\n\t"
                   "add w12, w12, #8\n\t"
                   "ldr x7, [x13, w12, sxtw]\n\t"
                   "add w12, w12, #8\n\t"
                   "add x13, x13, w12, sxtw\n\t"
                   "subs w12, w10, #8\n\t"
                   "b.le 2f\n\t"
                   "ldr x11, [x11]\n\t"
                   "lsl w12, w12, #3\n\t"
                   "sub SP, SP, w12, sxtw\n\t"
                   "tbz w12, #3, 1f\n\t"
                   "sub SP, SP, #8\n\t"
                   "1: sub w12, w12, #8\n\t"
                   "ldr x14, [x13, w12, sxtw]\n\t"
                   "str x14, [SP,  w12, sxtw]\n\t"
                   "cbnz w12, 1b\n\t"
                   "2: blr x9\n\t"
                   "mov SP, x29\n\t"
                   "ldp x29, x30, [SP], #16\n\t"
                   "ret\n" )

#else

static void CDECL _vcomp_fork_call_wrapper(void *wrapper, int nargs, __ms_va_list args)
{
    ERR("Not implemented for this architecture\n");
}

#endif

#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))

static inline char interlocked_cmpxchg8(char *dest, char xchg, char compare)
{
    char ret;
    __asm__ __volatile__( "lock; cmpxchgb %2,(%1)"
                          : "=a" (ret) : "r" (dest), "q" (xchg), "0" (compare) : "memory" );
    return ret;
}

static inline short interlocked_cmpxchg16(short *dest, short xchg, short compare)
{
    short ret;
    __asm__ __volatile__( "lock; cmpxchgw %2,(%1)"
                          : "=a" (ret) : "r" (dest), "r" (xchg), "0" (compare) : "memory" );
    return ret;
}

static inline char interlocked_xchg_add8(char *dest, char incr)
{
    char ret;
    __asm__ __volatile__( "lock; xaddb %0,(%1)"
                          : "=q" (ret) : "r" (dest), "0" (incr) : "memory" );
    return ret;
}

static inline short interlocked_xchg_add16(short *dest, short incr)
{
    short ret;
    __asm__ __volatile__( "lock; xaddw %0,(%1)"
                          : "=r" (ret) : "r" (dest), "0" (incr) : "memory" );
    return ret;
}

#else  /* __GNUC__ */

#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1
static inline char interlocked_cmpxchg8(char *dest, char xchg, char compare)
{
    return __sync_val_compare_and_swap(dest, compare, xchg);
}

static inline char interlocked_xchg_add8(char *dest, char incr)
{
    return __sync_fetch_and_add(dest, incr);
}
#else
static char interlocked_cmpxchg8(char *dest, char xchg, char compare)
{
    EnterCriticalSection(&vcomp_section);
    if (*dest == compare) *dest = xchg; else compare = *dest;
    LeaveCriticalSection(&vcomp_section);
    return compare;
}

static char interlocked_xchg_add8(char *dest, char incr)
{
    char ret;
    EnterCriticalSection(&vcomp_section);
    ret = *dest; *dest += incr;
    LeaveCriticalSection(&vcomp_section);
    return ret;
}
#endif

#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_2
static inline short interlocked_cmpxchg16(short *dest, short xchg, short compare)
{
    return __sync_val_compare_and_swap(dest, compare, xchg);
}

static inline short interlocked_xchg_add16(short *dest, short incr)
{
    return __sync_fetch_and_add(dest, incr);
}
#else
static short interlocked_cmpxchg16(short *dest, short xchg, short compare)
{
    EnterCriticalSection(&vcomp_section);
    if (*dest == compare) *dest = xchg; else compare = *dest;
    LeaveCriticalSection(&vcomp_section);
    return compare;
}

static short interlocked_xchg_add16(short *dest, short incr)
{
    short ret;
    EnterCriticalSection(&vcomp_section);
    ret = *dest; *dest += incr;
    LeaveCriticalSection(&vcomp_section);
    return ret;
}
#endif

#endif  /* __GNUC__ */

static inline struct vcomp_thread_data *vcomp_get_thread_data(void)
{
    return (struct vcomp_thread_data *)TlsGetValue(vcomp_context_tls);
}

static inline void vcomp_set_thread_data(struct vcomp_thread_data *thread_data)
{
    TlsSetValue(vcomp_context_tls, thread_data);
}

static struct vcomp_thread_data *vcomp_init_thread_data(void)
{
    struct vcomp_thread_data *thread_data = vcomp_get_thread_data();
    struct
    {
        struct vcomp_thread_data thread;
        struct vcomp_task_data   task;
    } *data;

    if (thread_data) return thread_data;
    if (!(data = HeapAlloc(GetProcessHeap(), 0, sizeof(*data))))
    {
        ERR("could not create thread data\n");
        ExitProcess(1);
    }

    data->task.single           = 0;
    data->task.section          = 0;
    data->task.dynamic          = 0;

    thread_data = &data->thread;
    thread_data->team           = NULL;
    thread_data->task           = &data->task;
    thread_data->thread_num     = 0;
    thread_data->parallel       = FALSE;
    thread_data->fork_threads   = 0;
    thread_data->single         = 1;
    thread_data->section        = 1;
    thread_data->dynamic        = 1;
    thread_data->dynamic_type   = 0;

    vcomp_set_thread_data(thread_data);
    return thread_data;
}

static void vcomp_free_thread_data(void)
{
    struct vcomp_thread_data *thread_data = vcomp_get_thread_data();
    if (!thread_data) return;

    HeapFree(GetProcessHeap(), 0, thread_data);
    vcomp_set_thread_data(NULL);
}

void CDECL _vcomp_atomic_add_i1(char *dest, char val)
{
    interlocked_xchg_add8(dest, val);
}

void CDECL _vcomp_atomic_and_i1(char *dest, char val)
{
    char old;
    do old = *dest; while (interlocked_cmpxchg8(dest, old & val, old) != old);
}

void CDECL _vcomp_atomic_div_i1(char *dest, char val)
{
    char old;
    do old = *dest; while (interlocked_cmpxchg8(dest, old / val, old) != old);
}

void CDECL _vcomp_atomic_div_ui1(unsigned char *dest, unsigned char val)
{
    unsigned char old;
    do old = *dest; while ((unsigned char)interlocked_cmpxchg8((char *)dest, old / val, old) != old);
}

void CDECL _vcomp_atomic_mul_i1(char *dest, char val)
{
    char old;
    do old = *dest; while (interlocked_cmpxchg8(dest, old * val, old) != old);
}

void CDECL _vcomp_atomic_or_i1(char *dest, char val)
{
    char old;
    do old = *dest; while (interlocked_cmpxchg8(dest, old | val, old) != old);
}

void CDECL _vcomp_atomic_shl_i1(char *dest, unsigned int val)
{
    char old;
    do old = *dest; while (interlocked_cmpxchg8(dest, old << val, old) != old);
}

void CDECL _vcomp_atomic_shr_i1(char *dest, unsigned int val)
{
    char old;
    do old = *dest; while (interlocked_cmpxchg8(dest, old >> val, old) != old);
}

void CDECL _vcomp_atomic_shr_ui1(unsigned char *dest, unsigned int val)
{
    unsigned char old;
    do old = *dest; while ((unsigned char)interlocked_cmpxchg8((char *)dest, old >> val, old) != old);
}

void CDECL _vcomp_atomic_sub_i1(char *dest, char val)
{
    interlocked_xchg_add8(dest, -val);
}

void CDECL _vcomp_atomic_xor_i1(char *dest, char val)
{
    char old;
    do old = *dest; while (interlocked_cmpxchg8(dest, old ^ val, old) != old);
}

static void CDECL _vcomp_atomic_bool_and_i1(char *dest, char val)
{
    char old;
    do old = *dest; while (interlocked_cmpxchg8(dest, old && val, old) != old);
}

static void CDECL _vcomp_atomic_bool_or_i1(char *dest, char val)
{
    char old;
    do old = *dest; while (interlocked_cmpxchg8(dest, old ? old : (val != 0), old) != old);
}

void CDECL _vcomp_reduction_i1(unsigned int flags, char *dest, char val)
{
    static void (CDECL * const funcs[])(char *, char) =
    {
        _vcomp_atomic_add_i1,
        _vcomp_atomic_add_i1,
        _vcomp_atomic_mul_i1,
        _vcomp_atomic_and_i1,
        _vcomp_atomic_or_i1,
        _vcomp_atomic_xor_i1,
        _vcomp_atomic_bool_and_i1,
        _vcomp_atomic_bool_or_i1,
    };
    unsigned int op = (flags >> 8) & 0xf;
    op = min(op, sizeof(funcs)/sizeof(funcs[0]) - 1);
    funcs[op](dest, val);
}

void CDECL _vcomp_atomic_add_i2(short *dest, short val)
{
    interlocked_xchg_add16(dest, val);
}

void CDECL _vcomp_atomic_and_i2(short *dest, short val)
{
    short old;
    do old = *dest; while (interlocked_cmpxchg16(dest, old & val, old) != old);
}

void CDECL _vcomp_atomic_div_i2(short *dest, short val)
{
    short old;
    do old = *dest; while (interlocked_cmpxchg16(dest, old / val, old) != old);
}

void CDECL _vcomp_atomic_div_ui2(unsigned short *dest, unsigned short val)
{
    unsigned short old;
    do old = *dest; while ((unsigned short)interlocked_cmpxchg16((short *)dest, old / val, old) != old);
}

void CDECL _vcomp_atomic_mul_i2(short *dest, short val)
{
    short old;
    do old = *dest; while (interlocked_cmpxchg16(dest, old * val, old) != old);
}

void CDECL _vcomp_atomic_or_i2(short *dest, short val)
{
    short old;
    do old = *dest; while (interlocked_cmpxchg16(dest, old | val, old) != old);
}

void CDECL _vcomp_atomic_shl_i2(short *dest, unsigned int val)
{
    short old;
    do old = *dest; while (interlocked_cmpxchg16(dest, old << val, old) != old);
}

void CDECL _vcomp_atomic_shr_i2(short *dest, unsigned int val)
{
    short old;
    do old = *dest; while (interlocked_cmpxchg16(dest, old >> val, old) != old);
}

void CDECL _vcomp_atomic_shr_ui2(unsigned short *dest, unsigned int val)
{
    unsigned short old;
    do old = *dest; while ((unsigned short)interlocked_cmpxchg16((short *)dest, old >> val, old) != old);
}

void CDECL _vcomp_atomic_sub_i2(short *dest, short val)
{
    interlocked_xchg_add16(dest, -val);
}

void CDECL _vcomp_atomic_xor_i2(short *dest, short val)
{
    short old;
    do old = *dest; while (interlocked_cmpxchg16(dest, old ^ val, old) != old);
}

static void CDECL _vcomp_atomic_bool_and_i2(short *dest, short val)
{
    short old;
    do old = *dest; while (interlocked_cmpxchg16(dest, old && val, old) != old);
}

static void CDECL _vcomp_atomic_bool_or_i2(short *dest, short val)
{
    short old;
    do old = *dest; while (interlocked_cmpxchg16(dest, old ? old : (val != 0), old) != old);
}

void CDECL _vcomp_reduction_i2(unsigned int flags, short *dest, short val)
{
    static void (CDECL * const funcs[])(short *, short) =
    {
        _vcomp_atomic_add_i2,
        _vcomp_atomic_add_i2,
        _vcomp_atomic_mul_i2,
        _vcomp_atomic_and_i2,
        _vcomp_atomic_or_i2,
        _vcomp_atomic_xor_i2,
        _vcomp_atomic_bool_and_i2,
        _vcomp_atomic_bool_or_i2,
    };
    unsigned int op = (flags >> 8) & 0xf;
    op = min(op, sizeof(funcs)/sizeof(funcs[0]) - 1);
    funcs[op](dest, val);
}

void CDECL _vcomp_atomic_add_i4(int *dest, int val)
{
    interlocked_xchg_add(dest, val);
}

void CDECL _vcomp_atomic_and_i4(int *dest, int val)
{
    int old;
    do old = *dest; while (interlocked_cmpxchg(dest, old & val, old) != old);
}

void CDECL _vcomp_atomic_div_i4(int *dest, int val)
{
    int old;
    do old = *dest; while (interlocked_cmpxchg(dest, old / val, old) != old);
}

void CDECL _vcomp_atomic_div_ui4(unsigned int *dest, unsigned int val)
{
    unsigned int old;
    do old = *dest; while (interlocked_cmpxchg((int *)dest, old / val, old) != old);
}

void CDECL _vcomp_atomic_mul_i4(int *dest, int val)
{
    int old;
    do old = *dest; while (interlocked_cmpxchg(dest, old * val, old) != old);
}

void CDECL _vcomp_atomic_or_i4(int *dest, int val)
{
    int old;
    do old = *dest; while (interlocked_cmpxchg(dest, old | val, old) != old);
}

void CDECL _vcomp_atomic_shl_i4(int *dest, int val)
{
    int old;
    do old = *dest; while (interlocked_cmpxchg(dest, old << val, old) != old);
}

void CDECL _vcomp_atomic_shr_i4(int *dest, int val)
{
    int old;
    do old = *dest; while (interlocked_cmpxchg(dest, old >> val, old) != old);
}

void CDECL _vcomp_atomic_shr_ui4(unsigned int *dest, unsigned int val)
{
    unsigned int old;
    do old = *dest; while (interlocked_cmpxchg((int *)dest, old >> val, old) != old);
}

void CDECL _vcomp_atomic_sub_i4(int *dest, int val)
{
    interlocked_xchg_add(dest, -val);
}

void CDECL _vcomp_atomic_xor_i4(int *dest, int val)
{
    int old;
    do old = *dest; while (interlocked_cmpxchg(dest, old ^ val, old) != old);
}

static void CDECL _vcomp_atomic_bool_and_i4(int *dest, int val)
{
    int old;
    do old = *dest; while (interlocked_cmpxchg(dest, old && val, old) != old);
}

static void CDECL _vcomp_atomic_bool_or_i4(int *dest, int val)
{
    int old;
    do old = *dest; while (interlocked_cmpxchg(dest, old ? old : (val != 0), old) != old);
}

void CDECL _vcomp_reduction_i4(unsigned int flags, int *dest, int val)
{
    static void (CDECL * const funcs[])(int *, int) =
    {
        _vcomp_atomic_add_i4,
        _vcomp_atomic_add_i4,
        _vcomp_atomic_mul_i4,
        _vcomp_atomic_and_i4,
        _vcomp_atomic_or_i4,
        _vcomp_atomic_xor_i4,
        _vcomp_atomic_bool_and_i4,
        _vcomp_atomic_bool_or_i4,
    };
    unsigned int op = (flags >> 8) & 0xf;
    op = min(op, sizeof(funcs)/sizeof(funcs[0]) - 1);
    funcs[op](dest, val);
}

void CDECL _vcomp_atomic_add_i8(LONG64 *dest, LONG64 val)
{
    LONG64 old;
    do old = *dest; while (interlocked_cmpxchg64(dest, old + val, old) != old);
}

void CDECL _vcomp_atomic_and_i8(LONG64 *dest, LONG64 val)
{
    LONG64 old;
    do old = *dest; while (interlocked_cmpxchg64(dest, old & val, old) != old);
}

void CDECL _vcomp_atomic_div_i8(LONG64 *dest, LONG64 val)
{
    LONG64 old;
    do old = *dest; while (interlocked_cmpxchg64(dest, old / val, old) != old);
}

void CDECL _vcomp_atomic_div_ui8(ULONG64 *dest, ULONG64 val)
{
    ULONG64 old;
    do old = *dest; while (interlocked_cmpxchg64((LONG64 *)dest, old / val, old) != old);
}

void CDECL _vcomp_atomic_mul_i8(LONG64 *dest, LONG64 val)
{
    LONG64 old;
    do old = *dest; while (interlocked_cmpxchg64(dest, old * val, old) != old);
}

void CDECL _vcomp_atomic_or_i8(LONG64 *dest, LONG64 val)
{
    LONG64 old;
    do old = *dest; while (interlocked_cmpxchg64(dest, old | val, old) != old);
}

void CDECL _vcomp_atomic_shl_i8(LONG64 *dest, unsigned int val)
{
    LONG64 old;
    do old = *dest; while (interlocked_cmpxchg64(dest, old << val, old) != old);
}

void CDECL _vcomp_atomic_shr_i8(LONG64 *dest, unsigned int val)
{
    LONG64 old;
    do old = *dest; while (interlocked_cmpxchg64(dest, old >> val, old) != old);
}

void CDECL _vcomp_atomic_shr_ui8(ULONG64 *dest, unsigned int val)
{
    ULONG64 old;
    do old = *dest; while (interlocked_cmpxchg64((LONG64 *)dest, old >> val, old) != old);
}

void CDECL _vcomp_atomic_sub_i8(LONG64 *dest, LONG64 val)
{
    LONG64 old;
    do old = *dest; while (interlocked_cmpxchg64(dest, old - val, old) != old);
}

void CDECL _vcomp_atomic_xor_i8(LONG64 *dest, LONG64 val)
{
    LONG64 old;
    do old = *dest; while (interlocked_cmpxchg64(dest, old ^ val, old) != old);
}

static void CDECL _vcomp_atomic_bool_and_i8(LONG64 *dest, LONG64 val)
{
    LONG64 old;
    do old = *dest; while (interlocked_cmpxchg64(dest, old && val, old) != old);
}

static void CDECL _vcomp_atomic_bool_or_i8(LONG64 *dest, LONG64 val)
{
    LONG64 old;
    do old = *dest; while (interlocked_cmpxchg64(dest, old ? old : (val != 0), old) != old);
}

void CDECL _vcomp_reduction_i8(unsigned int flags, LONG64 *dest, LONG64 val)
{
    static void (CDECL * const funcs[])(LONG64 *, LONG64) =
    {
        _vcomp_atomic_add_i8,
        _vcomp_atomic_add_i8,
        _vcomp_atomic_mul_i8,
        _vcomp_atomic_and_i8,
        _vcomp_atomic_or_i8,
        _vcomp_atomic_xor_i8,
        _vcomp_atomic_bool_and_i8,
        _vcomp_atomic_bool_or_i8,
    };
    unsigned int op = (flags >> 8) & 0xf;
    op = min(op, sizeof(funcs)/sizeof(funcs[0]) - 1);
    funcs[op](dest, val);
}

void CDECL _vcomp_atomic_add_r4(float *dest, float val)
{
    int old, new;
    do
    {
        old = *(int *)dest;
        *(float *)&new = *(float *)&old + val;
    }
    while (interlocked_cmpxchg((int *)dest, new, old) != old);
}

void CDECL _vcomp_atomic_div_r4(float *dest, float val)
{
    int old, new;
    do
    {
        old = *(int *)dest;
        *(float *)&new = *(float *)&old / val;
    }
    while (interlocked_cmpxchg((int *)dest, new, old) != old);
}

void CDECL _vcomp_atomic_mul_r4(float *dest, float val)
{
    int old, new;
    do
    {
        old = *(int *)dest;
        *(float *)&new = *(float *)&old * val;
    }
    while (interlocked_cmpxchg((int *)dest, new, old) != old);
}

void CDECL _vcomp_atomic_sub_r4(float *dest, float val)
{
    int old, new;
    do
    {
        old = *(int *)dest;
        *(float *)&new = *(float *)&old - val;
    }
    while (interlocked_cmpxchg((int *)dest, new, old) != old);
}

static void CDECL _vcomp_atomic_bool_and_r4(float *dest, float val)
{
    int old, new;
    do
    {
        old = *(int *)dest;
        *(float *)&new = (*(float *)&old != 0.0) ? (val != 0.0) : 0.0;
    }
    while (interlocked_cmpxchg((int *)dest, new, old) != old);
}

static void CDECL _vcomp_atomic_bool_or_r4(float *dest, float val)
{
    int old, new;
    do
    {
        old = *(int *)dest;
        *(float *)&new = (*(float *)&old != 0.0) ? *(float *)&old : (val != 0.0);
    }
    while (interlocked_cmpxchg((int *)dest, new, old) != old);
}

void CDECL _vcomp_reduction_r4(unsigned int flags, float *dest, float val)
{
    static void (CDECL * const funcs[])(float *, float) =
    {
        _vcomp_atomic_add_r4,
        _vcomp_atomic_add_r4,
        _vcomp_atomic_mul_r4,
        _vcomp_atomic_bool_or_r4,
        _vcomp_atomic_bool_or_r4,
        _vcomp_atomic_bool_or_r4,
        _vcomp_atomic_bool_and_r4,
        _vcomp_atomic_bool_or_r4,
    };
    unsigned int op = (flags >> 8) & 0xf;
    op = min(op, sizeof(funcs)/sizeof(funcs[0]) - 1);
    funcs[op](dest, val);
}

void CDECL _vcomp_atomic_add_r8(double *dest, double val)
{
    LONG64 old, new;
    do
    {
        old = *(LONG64 *)dest;
        *(double *)&new = *(double *)&old + val;
    }
    while (interlocked_cmpxchg64((LONG64 *)dest, new, old) != old);
}

void CDECL _vcomp_atomic_div_r8(double *dest, double val)
{
    LONG64 old, new;
    do
    {
        old = *(LONG64 *)dest;
        *(double *)&new = *(double *)&old / val;
    }
    while (interlocked_cmpxchg64((LONG64 *)dest, new, old) != old);
}

void CDECL _vcomp_atomic_mul_r8(double *dest, double val)
{
    LONG64 old, new;
    do
    {
        old = *(LONG64 *)dest;
        *(double *)&new = *(double *)&old * val;
    }
    while (interlocked_cmpxchg64((LONG64 *)dest, new, old) != old);
}

void CDECL _vcomp_atomic_sub_r8(double *dest, double val)
{
    LONG64 old, new;
    do
    {
        old = *(LONG64 *)dest;
        *(double *)&new = *(double *)&old - val;
    }
    while (interlocked_cmpxchg64((LONG64 *)dest, new, old) != old);
}

static void CDECL _vcomp_atomic_bool_and_r8(double *dest, double val)
{
    LONG64 old, new;
    do
    {
        old = *(LONG64 *)dest;
        *(double *)&new = (*(double *)&old != 0.0) ? (val != 0.0) : 0.0;
    }
    while (interlocked_cmpxchg64((LONG64 *)dest, new, old) != old);
}

static void CDECL _vcomp_atomic_bool_or_r8(double *dest, double val)
{
    LONG64 old, new;
    do
    {
        old = *(LONG64 *)dest;
        *(double *)&new = (*(double *)&old != 0.0) ? *(double *)&old : (val != 0.0);
    }
    while (interlocked_cmpxchg64((LONG64 *)dest, new, old) != old);
}

void CDECL _vcomp_reduction_r8(unsigned int flags, double *dest, double val)
{
    static void (CDECL * const funcs[])(double *, double) =
    {
        _vcomp_atomic_add_r8,
        _vcomp_atomic_add_r8,
        _vcomp_atomic_mul_r8,
        _vcomp_atomic_bool_or_r8,
        _vcomp_atomic_bool_or_r8,
        _vcomp_atomic_bool_or_r8,
        _vcomp_atomic_bool_and_r8,
        _vcomp_atomic_bool_or_r8,
    };
    unsigned int op = (flags >> 8) & 0xf;
    op = min(op, sizeof(funcs)/sizeof(funcs[0]) - 1);
    funcs[op](dest, val);
}

int CDECL omp_get_dynamic(void)
{
    TRACE("stub\n");
    return 0;
}

int CDECL omp_get_max_threads(void)
{
    TRACE("()\n");
    return vcomp_max_threads;
}

int CDECL omp_get_nested(void)
{
    TRACE("stub\n");
    return vcomp_nested_fork;
}

int CDECL omp_get_num_procs(void)
{
    TRACE("stub\n");
    return 1;
}

int CDECL omp_get_num_threads(void)
{
    struct vcomp_team_data *team_data = vcomp_init_thread_data()->team;
    TRACE("()\n");
    return team_data ? team_data->num_threads : 1;
}

int CDECL omp_get_thread_num(void)
{
    TRACE("()\n");
    return vcomp_init_thread_data()->thread_num;
}

int CDECL _vcomp_get_thread_num(void)
{
    TRACE("()\n");
    return vcomp_init_thread_data()->thread_num;
}

/* Time in seconds since "some time in the past" */
double CDECL omp_get_wtime(void)
{
    return GetTickCount() / 1000.0;
}

void CDECL omp_set_dynamic(int val)
{
    TRACE("(%d): stub\n", val);
}

void CDECL omp_set_nested(int nested)
{
    TRACE("(%d)\n", nested);
    vcomp_nested_fork = (nested != 0);
}

void CDECL omp_set_num_threads(int num_threads)
{
    TRACE("(%d)\n", num_threads);
    if (num_threads >= 1)
        vcomp_num_threads = num_threads;
}

void CDECL _vcomp_flush(void)
{
    TRACE("(): stub\n");
}

void CDECL _vcomp_barrier(void)
{
    struct vcomp_team_data *team_data = vcomp_init_thread_data()->team;

    TRACE("()\n");

    if (!team_data)
        return;

    EnterCriticalSection(&vcomp_section);
    if (++team_data->barrier_count >= team_data->num_threads)
    {
        team_data->barrier++;
        team_data->barrier_count = 0;
        WakeAllConditionVariable(&team_data->cond);
    }
    else
    {
        unsigned int barrier = team_data->barrier;
        while (team_data->barrier == barrier)
            SleepConditionVariableCS(&team_data->cond, &vcomp_section, INFINITE);
    }
    LeaveCriticalSection(&vcomp_section);
}

void CDECL _vcomp_set_num_threads(int num_threads)
{
    TRACE("(%d)\n", num_threads);
    if (num_threads >= 1)
        vcomp_init_thread_data()->fork_threads = num_threads;
}

int CDECL _vcomp_master_begin(void)
{
    TRACE("()\n");
    return !vcomp_init_thread_data()->thread_num;
}

void CDECL _vcomp_master_end(void)
{
    TRACE("()\n");
    /* nothing to do here */
}

int CDECL _vcomp_single_begin(int flags)
{
    struct vcomp_thread_data *thread_data = vcomp_init_thread_data();
    struct vcomp_task_data *task_data = thread_data->task;
    int ret = FALSE;

    TRACE("(%x): semi-stub\n", flags);

    EnterCriticalSection(&vcomp_section);
    thread_data->single++;
    if ((int)(thread_data->single - task_data->single) > 0)
    {
        task_data->single = thread_data->single;
        ret = TRUE;
    }
    LeaveCriticalSection(&vcomp_section);

    return ret;
}

void CDECL _vcomp_single_end(void)
{
    TRACE("()\n");
    /* nothing to do here */
}

void CDECL _vcomp_sections_init(int n)
{
    struct vcomp_thread_data *thread_data = vcomp_init_thread_data();
    struct vcomp_task_data *task_data = thread_data->task;

    TRACE("(%d)\n", n);

    EnterCriticalSection(&vcomp_section);
    thread_data->section++;
    if ((int)(thread_data->section - task_data->section) > 0)
    {
        task_data->section       = thread_data->section;
        task_data->num_sections  = n;
        task_data->section_index = 0;
    }
    LeaveCriticalSection(&vcomp_section);
}

int CDECL _vcomp_sections_next(void)
{
    struct vcomp_thread_data *thread_data = vcomp_init_thread_data();
    struct vcomp_task_data *task_data = thread_data->task;
    int i = -1;

    TRACE("()\n");

    EnterCriticalSection(&vcomp_section);
    if (thread_data->section == task_data->section &&
        task_data->section_index != task_data->num_sections)
    {
        i = task_data->section_index++;
    }
    LeaveCriticalSection(&vcomp_section);
    return i;
}

void CDECL _vcomp_for_static_simple_init(unsigned int first, unsigned int last, int step,
                                         BOOL increment, unsigned int *begin, unsigned int *end)
{
    unsigned int iterations, per_thread, remaining;
    struct vcomp_thread_data *thread_data = vcomp_init_thread_data();
    struct vcomp_team_data *team_data = thread_data->team;
    int num_threads = team_data ? team_data->num_threads : 1;
    int thread_num = thread_data->thread_num;

    TRACE("(%u, %u, %d, %u, %p, %p)\n", first, last, step, increment, begin, end);

    if (num_threads == 1)
    {
        *begin = first;
        *end   = last;
        return;
    }

    if (step <= 0)
    {
        *begin = 0;
        *end   = increment ? -1 : 1;
        return;
    }

    if (increment)
        iterations = 1 + (last - first) / step;
    else
    {
        iterations = 1 + (first - last) / step;
        step *= -1;
    }

    per_thread = iterations / num_threads;
    remaining  = iterations - per_thread * num_threads;

    if (thread_num < remaining)
        per_thread++;
    else if (per_thread)
        first += remaining * step;
    else
    {
        *begin = first;
        *end   = first - step;
        return;
    }

    *begin = first + per_thread * thread_num * step;
    *end   = *begin + (per_thread - 1) * step;
}

void CDECL _vcomp_for_static_init(int first, int last, int step, int chunksize, unsigned int *loops,
                                  int *begin, int *end, int *next, int *lastchunk)
{
    unsigned int iterations, num_chunks, per_thread, remaining;
    struct vcomp_thread_data *thread_data = vcomp_init_thread_data();
    struct vcomp_team_data *team_data = thread_data->team;
    int num_threads = team_data ? team_data->num_threads : 1;
    int thread_num = thread_data->thread_num;
    int no_begin, no_lastchunk;

    TRACE("(%d, %d, %d, %d, %p, %p, %p, %p, %p)\n",
          first, last, step, chunksize, loops, begin, end, next, lastchunk);

    if (!begin)
    {
        begin = &no_begin;
        lastchunk = &no_lastchunk;
    }

    if (num_threads == 1 && chunksize != 1)
    {
        *loops      = 1;
        *begin      = first;
        *end        = last;
        *next       = 0;
        *lastchunk  = first;
        return;
    }

    if (first == last)
    {
        *loops = !thread_num;
        if (!thread_num)
        {
            *begin      = first;
            *end        = last;
            *next       = 0;
            *lastchunk  = first;
        }
        return;
    }

    if (step <= 0)
    {
        *loops = 0;
        return;
    }

    if (first < last)
        iterations = 1 + (last - first) / step;
    else
    {
        iterations = 1 + (first - last) / step;
        step *= -1;
    }

    if (chunksize < 1)
        chunksize = 1;

    num_chunks  = ((DWORD64)iterations + chunksize - 1) / chunksize;
    per_thread  = num_chunks / num_threads;
    remaining   = num_chunks - per_thread * num_threads;

    *loops      = per_thread + (thread_num < remaining);
    *begin      = first + thread_num * chunksize * step;
    *end        = *begin + (chunksize - 1) * step;
    *next       = chunksize * num_threads * step;
    *lastchunk  = first + (num_chunks - 1) * chunksize * step;
}

void CDECL _vcomp_for_static_end(void)
{
    TRACE("()\n");
    /* nothing to do here */
}

void CDECL _vcomp_for_dynamic_init(unsigned int flags, unsigned int first, unsigned int last,
                                   int step, unsigned int chunksize)
{
    unsigned int iterations, per_thread, remaining;
    struct vcomp_thread_data *thread_data = vcomp_init_thread_data();
    struct vcomp_team_data *team_data = thread_data->team;
    struct vcomp_task_data *task_data = thread_data->task;
    int num_threads = team_data ? team_data->num_threads : 1;
    int thread_num = thread_data->thread_num;
    unsigned int type = flags & ~VCOMP_DYNAMIC_FLAGS_INCREMENT;

    TRACE("(%u, %u, %u, %d, %u)\n", flags, first, last, step, chunksize);

    if (step <= 0)
    {
        thread_data->dynamic_type = 0;
        return;
    }

    if (flags & VCOMP_DYNAMIC_FLAGS_INCREMENT)
        iterations = 1 + (last - first) / step;
    else
    {
        iterations = 1 + (first - last) / step;
        step *= -1;
    }

    if (type == VCOMP_DYNAMIC_FLAGS_STATIC)
    {
        per_thread = iterations / num_threads;
        remaining  = iterations - per_thread * num_threads;

        if (thread_num < remaining)
            per_thread++;
        else if (per_thread)
            first += remaining * step;
        else
        {
            thread_data->dynamic_type = 0;
            return;
        }

        thread_data->dynamic_type   = VCOMP_DYNAMIC_FLAGS_STATIC;
        thread_data->dynamic_begin  = first + per_thread * thread_num * step;
        thread_data->dynamic_end    = thread_data->dynamic_begin + (per_thread - 1) * step;
    }
    else
    {
        if (type != VCOMP_DYNAMIC_FLAGS_CHUNKED &&
            type != VCOMP_DYNAMIC_FLAGS_GUIDED)
        {
            FIXME("unsupported flags %u\n", flags);
            type = VCOMP_DYNAMIC_FLAGS_GUIDED;
        }

        EnterCriticalSection(&vcomp_section);
        thread_data->dynamic++;
        thread_data->dynamic_type = type;
        if ((int)(thread_data->dynamic - task_data->dynamic) > 0)
        {
            task_data->dynamic              = thread_data->dynamic;
            task_data->dynamic_first        = first;
            task_data->dynamic_last         = last;
            task_data->dynamic_iterations   = iterations;
            task_data->dynamic_step         = step;
            task_data->dynamic_chunksize    = chunksize;
        }
        LeaveCriticalSection(&vcomp_section);
    }
}

int CDECL _vcomp_for_dynamic_next(unsigned int *begin, unsigned int *end)
{
    struct vcomp_thread_data *thread_data = vcomp_init_thread_data();
    struct vcomp_task_data *task_data = thread_data->task;
    struct vcomp_team_data *team_data = thread_data->team;
    int num_threads = team_data ? team_data->num_threads : 1;

    TRACE("(%p, %p)\n", begin, end);

    if (thread_data->dynamic_type == VCOMP_DYNAMIC_FLAGS_STATIC)
    {
        *begin = thread_data->dynamic_begin;
        *end   = thread_data->dynamic_end;
        thread_data->dynamic_type = 0;
        return 1;
    }
    else if (thread_data->dynamic_type == VCOMP_DYNAMIC_FLAGS_CHUNKED ||
             thread_data->dynamic_type == VCOMP_DYNAMIC_FLAGS_GUIDED)
    {
        unsigned int iterations = 0;
        EnterCriticalSection(&vcomp_section);
        if (thread_data->dynamic == task_data->dynamic &&
            task_data->dynamic_iterations != 0)
        {
            iterations = min(task_data->dynamic_iterations, task_data->dynamic_chunksize);
            if (thread_data->dynamic_type == VCOMP_DYNAMIC_FLAGS_GUIDED &&
                task_data->dynamic_iterations > num_threads * task_data->dynamic_chunksize)
            {
                iterations = (task_data->dynamic_iterations + num_threads - 1) / num_threads;
            }
            *begin = task_data->dynamic_first;
            *end   = task_data->dynamic_first + (iterations - 1) * task_data->dynamic_step;
            task_data->dynamic_iterations -= iterations;
            task_data->dynamic_first      += iterations * task_data->dynamic_step;
            if (!task_data->dynamic_iterations)
                *end = task_data->dynamic_last;
        }
        LeaveCriticalSection(&vcomp_section);
        return iterations != 0;
    }

    return 0;
}

int CDECL omp_in_parallel(void)
{
    TRACE("()\n");
    return vcomp_init_thread_data()->parallel;
}

static DWORD WINAPI _vcomp_fork_worker(void *param)
{
    struct vcomp_thread_data *thread_data = param;
    vcomp_set_thread_data(thread_data);

    TRACE("starting worker thread for %p\n", thread_data);

    EnterCriticalSection(&vcomp_section);
    for (;;)
    {
        struct vcomp_team_data *team = thread_data->team;
        if (team != NULL)
        {
            LeaveCriticalSection(&vcomp_section);
            _vcomp_fork_call_wrapper(team->wrapper, team->nargs, team->valist);
            EnterCriticalSection(&vcomp_section);

            thread_data->team = NULL;
            list_remove(&thread_data->entry);
            list_add_tail(&vcomp_idle_threads, &thread_data->entry);
            if (++team->finished_threads >= team->num_threads)
                WakeAllConditionVariable(&team->cond);
        }

        if (!SleepConditionVariableCS(&thread_data->cond, &vcomp_section, 5000) &&
            GetLastError() == ERROR_TIMEOUT && !thread_data->team)
        {
            break;
        }
    }
    list_remove(&thread_data->entry);
    LeaveCriticalSection(&vcomp_section);

    TRACE("terminating worker thread for %p\n", thread_data);

    HeapFree(GetProcessHeap(), 0, thread_data);
    vcomp_set_thread_data(NULL);
    FreeLibraryAndExitThread(vcomp_module, 0);
    return 0;
}

void WINAPIV _vcomp_fork(BOOL ifval, int nargs, void *wrapper, ...)
{
    struct vcomp_thread_data *prev_thread_data = vcomp_init_thread_data();
    struct vcomp_thread_data thread_data;
    struct vcomp_team_data team_data;
    struct vcomp_task_data task_data;
    int num_threads;

    TRACE("(%d, %d, %p, ...)\n", ifval, nargs, wrapper);

    if (prev_thread_data->parallel && !vcomp_nested_fork)
        ifval = FALSE;

    if (!ifval)
        num_threads = 1;
    else if (prev_thread_data->fork_threads)
        num_threads = prev_thread_data->fork_threads;
    else
        num_threads = vcomp_num_threads;

    InitializeConditionVariable(&team_data.cond);
    team_data.num_threads       = 1;
    team_data.finished_threads  = 0;
    team_data.nargs             = nargs;
    team_data.wrapper           = wrapper;
    __ms_va_start(team_data.valist, wrapper);
    team_data.barrier           = 0;
    team_data.barrier_count     = 0;

    task_data.single            = 0;
    task_data.section           = 0;
    task_data.dynamic           = 0;

    thread_data.team            = &team_data;
    thread_data.task            = &task_data;
    thread_data.thread_num      = 0;
    thread_data.parallel        = ifval || prev_thread_data->parallel;
    thread_data.fork_threads    = 0;
    thread_data.single          = 1;
    thread_data.section         = 1;
    thread_data.dynamic         = 1;
    thread_data.dynamic_type    = 0;
    list_init(&thread_data.entry);
    InitializeConditionVariable(&thread_data.cond);

    if (num_threads > 1)
    {
        struct list *ptr;
        EnterCriticalSection(&vcomp_section);

        /* reuse existing threads (if any) */
        while (team_data.num_threads < num_threads && (ptr = list_head(&vcomp_idle_threads)))
        {
            struct vcomp_thread_data *data = LIST_ENTRY(ptr, struct vcomp_thread_data, entry);
            data->team          = &team_data;
            data->task          = &task_data;
            data->thread_num    = team_data.num_threads++;
            data->parallel      = thread_data.parallel;
            data->fork_threads  = 0;
            data->single        = 1;
            data->section       = 1;
            data->dynamic       = 1;
            data->dynamic_type  = 0;
            list_remove(&data->entry);
            list_add_tail(&thread_data.entry, &data->entry);
            WakeAllConditionVariable(&data->cond);
        }

        /* spawn additional threads */
        while (team_data.num_threads < num_threads)
        {
            struct vcomp_thread_data *data;
            HMODULE module;
            HANDLE thread;

            data = HeapAlloc(GetProcessHeap(), 0, sizeof(*data));
            if (!data) break;

            data->team          = &team_data;
            data->task          = &task_data;
            data->thread_num    = team_data.num_threads;
            data->parallel      = thread_data.parallel;
            data->fork_threads  = 0;
            data->single        = 1;
            data->section       = 1;
            data->dynamic       = 1;
            data->dynamic_type  = 0;
            InitializeConditionVariable(&data->cond);

            thread = CreateThread(NULL, 0, _vcomp_fork_worker, data, 0, NULL);
            if (!thread)
            {
                HeapFree(GetProcessHeap(), 0, data);
                break;
            }

            GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
                               (const WCHAR *)vcomp_module, &module);
            team_data.num_threads++;
            list_add_tail(&thread_data.entry, &data->entry);
            CloseHandle(thread);
        }

        LeaveCriticalSection(&vcomp_section);
    }

    vcomp_set_thread_data(&thread_data);
    _vcomp_fork_call_wrapper(team_data.wrapper, team_data.nargs, team_data.valist);
    vcomp_set_thread_data(prev_thread_data);
    prev_thread_data->fork_threads = 0;

    if (team_data.num_threads > 1)
    {
        EnterCriticalSection(&vcomp_section);

        team_data.finished_threads++;
        while (team_data.finished_threads < team_data.num_threads)
            SleepConditionVariableCS(&team_data.cond, &vcomp_section, INFINITE);

        LeaveCriticalSection(&vcomp_section);
        assert(list_empty(&thread_data.entry));
    }

    __ms_va_end(team_data.valist);
}

static CRITICAL_SECTION *alloc_critsect(void)
{
    CRITICAL_SECTION *critsect;
    if (!(critsect = HeapAlloc(GetProcessHeap(), 0, sizeof(*critsect))))
    {
        ERR("could not allocate critical section\n");
        ExitProcess(1);
    }

    InitializeCriticalSection(critsect);
    critsect->DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": critsect");
    return critsect;
}

static void destroy_critsect(CRITICAL_SECTION *critsect)
{
    if (!critsect) return;
    critsect->DebugInfo->Spare[0] = 0;
    DeleteCriticalSection(critsect);
    HeapFree(GetProcessHeap(), 0, critsect);
}

void CDECL omp_init_lock(omp_lock_t *lock)
{
    TRACE("(%p)\n", lock);
    *lock = alloc_critsect();
}

void CDECL omp_destroy_lock(omp_lock_t *lock)
{
    TRACE("(%p)\n", lock);
    destroy_critsect(*lock);
}

void CDECL omp_set_lock(omp_lock_t *lock)
{
    TRACE("(%p)\n", lock);

    if (RtlIsCriticalSectionLockedByThread(*lock))
    {
        ERR("omp_set_lock called while holding lock %p\n", *lock);
        ExitProcess(1);
    }

    EnterCriticalSection(*lock);
}

void CDECL omp_unset_lock(omp_lock_t *lock)
{
    TRACE("(%p)\n", lock);
    LeaveCriticalSection(*lock);
}

int CDECL omp_test_lock(omp_lock_t *lock)
{
    TRACE("(%p)\n", lock);

    if (RtlIsCriticalSectionLockedByThread(*lock))
        return 0;

    return TryEnterCriticalSection(*lock);
}

void CDECL omp_set_nest_lock(omp_nest_lock_t *lock)
{
    TRACE("(%p)\n", lock);
    EnterCriticalSection(*lock);
}

void CDECL omp_unset_nest_lock(omp_nest_lock_t *lock)
{
    TRACE("(%p)\n", lock);
    LeaveCriticalSection(*lock);
}

int CDECL omp_test_nest_lock(omp_nest_lock_t *lock)
{
    TRACE("(%p)\n", lock);
    return TryEnterCriticalSection(*lock) ? (*lock)->RecursionCount : 0;
}

void CDECL _vcomp_enter_critsect(CRITICAL_SECTION **critsect)
{
    TRACE("(%p)\n", critsect);

    if (!*critsect)
    {
        CRITICAL_SECTION *new_critsect = alloc_critsect();
        if (interlocked_cmpxchg_ptr((void **)critsect, new_critsect, NULL) != NULL)
            destroy_critsect(new_critsect);  /* someone beat us to it */
    }

    EnterCriticalSection(*critsect);
}

void CDECL _vcomp_leave_critsect(CRITICAL_SECTION *critsect)
{
    TRACE("(%p)\n", critsect);
    LeaveCriticalSection(critsect);
}

BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
{
    TRACE("(%p, %d, %p)\n", instance, reason, reserved);

    switch (reason)
    {
        case DLL_PROCESS_ATTACH:
        {
            SYSTEM_INFO sysinfo;

            if ((vcomp_context_tls = TlsAlloc()) == TLS_OUT_OF_INDEXES)
            {
                ERR("Failed to allocate TLS index\n");
                return FALSE;
            }

            GetSystemInfo(&sysinfo);
            vcomp_module      = instance;
            vcomp_max_threads = sysinfo.dwNumberOfProcessors;
            vcomp_num_threads = sysinfo.dwNumberOfProcessors;
            break;
        }

        case DLL_PROCESS_DETACH:
        {
            if (reserved) break;
            if (vcomp_context_tls != TLS_OUT_OF_INDEXES)
            {
                vcomp_free_thread_data();
                TlsFree(vcomp_context_tls);
            }
            break;
        }

        case DLL_THREAD_DETACH:
        {
            vcomp_free_thread_data();
            break;
        }
    }

    return TRUE;
}
