// RUN: %libomptarget-compile-run-and-check-generic
// XFAIL: intelgpu
// REQUIRES: gpu
#include <assert.h>
#include <omp.h>
#include <stdio.h>

// ---------------------------------------------------------------------------
// Various definitions copied from OpenMP RTL

typedef struct {
  uint64_t Reserved;
  uint16_t Version;
  uint16_t Kind; // OpenMP==1
  uint32_t Flags;
  void *Address;
  char *SymbolName;
  uint64_t Size;
  uint64_t Data;
  void *AuxAddr;
} __tgt_offload_entry;

enum OpenMPOffloadingDeclareTargetFlags {
  /// Mark the entry global as having a 'link' attribute.
  OMP_DECLARE_TARGET_LINK = 0x01,
  /// Mark the entry global as being an indirectly callable function.
  OMP_DECLARE_TARGET_INDIRECT = 0x08,
  /// This is an entry corresponding to a requirement to be registered.
  OMP_REGISTER_REQUIRES = 0x10,
  /// Mark the entry global as being an indirect vtable.
  OMP_DECLARE_TARGET_INDIRECT_VTABLE = 0x20,
};

#pragma omp begin declare variant match(device = {kind(gpu)})
// Provided by the runtime.
void *__llvm_omp_indirect_call_lookup(void *host_ptr);
#pragma omp declare target to(__llvm_omp_indirect_call_lookup)                 \
    device_type(nohost)
#pragma omp end declare variant

#pragma omp begin declare variant match(device = {kind(cpu)})
// We assume unified addressing on the CPU target.
void *__llvm_omp_indirect_call_lookup(void *host_ptr) { return host_ptr; }
#pragma omp end declare variant

#pragma omp begin declare target
void foo(int *i) { *i += 1; }
void bar(int *i) { *i += 10; }
void baz(int *i) { *i += 100; }
#pragma omp end declare target

typedef void (*fptr_t)(int *i);

// Dispatch Table - declare separately on host and device to avoid
// registering with the library; this also allows us to use separate
// names, which is convenient for debugging.  This dispatchTable is
// intended to mimic what Clang emits for C++ vtables.
fptr_t dispatchTable[] = {foo, bar, baz};
#pragma omp begin declare target device_type(nohost)
fptr_t GPUdispatchTable[] = {foo, bar, baz};
fptr_t *GPUdispatchTablePtr = GPUdispatchTable;
#pragma omp end declare target

// Define "manual" OpenMP offload entries, where we emit Clang
// offloading entry structure definitions in the appropriate ELF
// section.  This allows us to  emulate the offloading entries that Clang would
// normally emit for us

__attribute__((weak, section("llvm_offload_entries"), aligned(8)))
const __tgt_offload_entry __offloading_entry[] = {{
    0ULL,                               // Reserved
    1,                                  // Version
    1,                                  // Kind
    OMP_DECLARE_TARGET_INDIRECT_VTABLE, // Flags
    &dispatchTable,                     // Address
    "GPUdispatchTablePtr",              // SymbolName
    (size_t)(sizeof(dispatchTable)),    // Size
    0ULL,                               // Data
    NULL                                // AuxAddr
}};

// Mimic how Clang emits vtable pointers for C++ classes
typedef struct {
  fptr_t *dispatchPtr;
} myClass;

// ---------------------------------------------------------------------------
int main() {
  myClass obj_foo = {dispatchTable + 0};
  myClass obj_bar = {dispatchTable + 1};
  myClass obj_baz = {dispatchTable + 2};
  int aaa = 0;

#pragma omp target map(aaa) map(to : obj_foo, obj_bar, obj_baz)
  {
    // Lookup
    fptr_t *foo_ptr = __llvm_omp_indirect_call_lookup(obj_foo.dispatchPtr);
    fptr_t *bar_ptr = __llvm_omp_indirect_call_lookup(obj_bar.dispatchPtr);
    fptr_t *baz_ptr = __llvm_omp_indirect_call_lookup(obj_baz.dispatchPtr);
    foo_ptr[0](&aaa);
    bar_ptr[0](&aaa);
    baz_ptr[0](&aaa);
  }

  assert(aaa == 111);
  // CHECK: PASS
  printf("PASS\n");
  return 0;
}
