/*
 * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */
#include <jni.h>
#include <stdlib.h>

#ifdef AIX
#include <pthread.h>
#endif //AIX
#ifdef WINDOWS
#include <windows.h>
#else
#include <dlfcn.h>
#endif // WINDOWS

#ifdef WINDOWS
  HMODULE handle;
#else
  void* handle;
#endif // WINDOWS

jint(JNICALL *jni_create_java_vm)(JavaVM **, JNIEnv **, void *) = NULL;

// method to perform dlclose on an open dynamic library handle
void closeHandle() {
#ifdef WINDOWS
  if (!FreeLibrary(handle)) {
   fprintf(stderr, "Error occurred while closing handle: 0x%02X\n", GetLastError());
  }
#else
  if (dlclose(handle) != 0) {
    fprintf(stderr, "Error occurred while closing handle: %s\n", dlerror());
  }
#endif // WINDOWS
}

void fail(int code) {
  if (handle) {
    closeHandle();
  }
  exit(code);
}


// method to load the dynamic library libjvm
int loadJVM(const char* path) {
#ifdef WINDOWS
  UINT errorMode = GetErrorMode();
  SetErrorMode(errorMode | SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
  handle = LoadLibraryA(path);
#else
  handle = dlopen(path, RTLD_LAZY);
#endif // WINDOWS

  if (handle) {
    // find the address of function
#ifdef WINDOWS
    *(void **) (&jni_create_java_vm) = GetProcAddress(handle, "JNI_CreateJavaVM");
#else
    *(void **) (&jni_create_java_vm) = dlsym(handle, "JNI_CreateJavaVM");
#endif // WINDOWS

    if (jni_create_java_vm == NULL) {
      fprintf(stderr, "ERROR: No JNI_CreateJavaVM found: '%s'\n", path);
      return -1;
    }
  } else {
#ifdef WINDOWS
    fprintf(stderr, "ERROR: Can't load JVM library: 0x%02X\n", GetLastError());
#else
    fprintf(stderr, "ERROR: Can't load JVM library: %s\n", dlerror());
#endif // WINDOWS
    return -1;
  }
  return 0;
}

long long unsigned int d2l(double d) {
  union {
    double d;
    long long unsigned int llu;
  } dl;

  dl.d = d;
  return dl.llu;
}

#define print_reg(r) printf("%s = %f (0x%llX)\n", #r, r, d2l(r));

typedef struct {
  int argc;
  char **argv;
} args_list;

static void* run(void* argp) {
  args_list *arg = (args_list*) argp;
  int argc =  arg->argc;
  char **argv = arg->argv;
  JavaVM* jvm;
  JNIEnv* env;
  JavaVMInitArgs vm_args;

  // values to trick constant folding
  long long unsigned int vd[32];
  int i;
  int bad_cnt = 0;

  // values occupy fp registers
  // note: suitable code shape is produced only on Windows,
  // and even then registers are corrupted not on every machine
  register double d00;
  register double d01;
  register double d02;
  register double d03;
  register double d04;
  register double d05;
  register double d06;
  register double d07;
  register double d08;
  register double d09;
  register double d10;
  register double d11;
  register double d12;
  register double d13;
  register double d14;
  register double d15;

  if (argc != 2) {
    printf("Usage: FPRegs <jvm_path>");
    fail(2);
  }
  printf("jvm_path = %s\n", argv[1]);

  if (loadJVM(argv[1]) < 0) {
    fail(3);
  }

  vm_args.version = JNI_VERSION_1_8;
  vm_args.ignoreUnrecognized = JNI_FALSE;
  vm_args.options = NULL;
  vm_args.nOptions = 0;

  for(i = 0; i < 16; i++) {
    vd[i] = d2l(100 + i);
  }

  d00 = 100.0;
  d01 = 101.0;
  d02 = 102.0;
  d03 = 103.0;
  d04 = 104.0;
  d05 = 105.0;
  d06 = 106.0;
  d07 = 107.0;
  d08 = 108.0;
  d09 = 109.0;
  d10 = 110.0;
  d11 = 111.0;
  d12 = 112.0;
  d13 = 113.0;
  d14 = 114.0;
  d15 = 115.0;

  printf("BEFORE:\n");
  print_reg(d00);
  print_reg(d01);
  print_reg(d02);
  print_reg(d03);
  print_reg(d04);
  print_reg(d05);
  print_reg(d06);
  print_reg(d07);
  print_reg(d08);
  print_reg(d09);
  print_reg(d10);
  print_reg(d11);
  print_reg(d12);
  print_reg(d13);
  print_reg(d14);
  print_reg(d15);

  if (jni_create_java_vm(&jvm, &env, &vm_args) < 0 ) {
    fprintf(stderr, "ERROR: Can't create JavaVM\n");
    fail(4);
  }

  if (d2l(d00) != vd[0]) bad_cnt++;
  if (d2l(d01) != vd[1]) bad_cnt++;
  if (d2l(d02) != vd[2]) bad_cnt++;
  if (d2l(d03) != vd[3]) bad_cnt++;
  if (d2l(d04) != vd[4]) bad_cnt++;
  if (d2l(d05) != vd[5]) bad_cnt++;
  if (d2l(d06) != vd[6]) bad_cnt++;
  if (d2l(d07) != vd[7]) bad_cnt++;
  if (d2l(d08) != vd[8]) bad_cnt++;
  if (d2l(d09) != vd[9]) bad_cnt++;
  if (d2l(d10) != vd[10]) bad_cnt++;
  if (d2l(d11) != vd[11]) bad_cnt++;
  if (d2l(d12) != vd[12]) bad_cnt++;
  if (d2l(d13) != vd[13]) bad_cnt++;
  if (d2l(d14) != vd[14]) bad_cnt++;
  if (d2l(d15) != vd[15]) bad_cnt++;

  printf("AFTER:\n");
  print_reg(d00);
  print_reg(d01);
  print_reg(d02);
  print_reg(d03);
  print_reg(d04);
  print_reg(d05);
  print_reg(d06);
  print_reg(d07);
  print_reg(d08);
  print_reg(d09);
  print_reg(d10);
  print_reg(d11);
  print_reg(d12);
  print_reg(d13);
  print_reg(d14);
  print_reg(d15);

  printf("%d registers corrupted\n", bad_cnt);
  if (bad_cnt > 0) {
      printf("TEST FAILED");
      fail(1);
  }

  printf("TEST PASSED");
  closeHandle();
  return 0;
}

int main(int argc, char *argv[]) {
  args_list args;
  args.argc = argc;
  args.argv = argv;
#ifdef AIX
  size_t adjusted_stack_size = 1024*1024;
  pthread_t id;
  int result;
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setstacksize(&attr, adjusted_stack_size);
  result = pthread_create(&id, &attr, run, (void *)&args);
  if (result != 0) {
    fprintf(stderr, "Error: pthread_create failed with error code %d \n", result);
    return -1;
  }
  pthread_join(id, NULL);
#else
  run(&args);
#endif //AIX
}
