// Copyright 2019 The Emscripten Authors.  All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License.  Both these licenses can be
// found in the LICENSE file.

#include <pthread.h>
#include <stdlib.h>
#include <assert.h>
#include <emscripten/em_js.h>
#include <stdbool.h>

// We want to test that JavaScript access in the main thread automatically
// updates the memory views if the heap has grown from the pthread meanwhile.
//
// Checking this correctly is somewhat tricky because a lot of Emscripten APIs
// access heap on their own, so the memory views might be updated by them before
// we explicitly check the state in our own JS routines below, which leads to
// false positives (test passing even though in isolation the heap access in our
// own JS is not the one updating the memory views).
//
// To test it in isolation, we need to use only EM_JS - which is as close to a
// pure JS call as we can get - and not EM_ASM, pthread_join, proxying etc., not
// even `puts`/`printf` for logging - as all of those access the heap from JS on
// their own and might update the memory views too early. Not using standard
// proxying mechanisms also means we need to drop down all the way to raw
// atomics.

EM_JS(void, assert_initial_heap_state, (bool isWorker), {
  err(`Checking initial heap state on the ${isWorker ? 'worker' : 'main'} thread`);
  assert(HEAP8.length === 32 * 1024 * 1024, "start at 32MB");
});

EM_JS(void, js_assert_final_heap_state, (bool isWorker, const char* buffer, int finalHeapSize), {
  err(`Checking final heap state on the ${isWorker ? 'worker' : 'main'} thread`);
  assert(HEAP8.length > finalHeapSize, "end with >64MB");
  assert(HEAP8[buffer] === 42, "readable from JS");
});

#define FINAL_HEAP_SIZE (64 * 1024 * 1024)

char *alloc_beyond_initial_heap() {
  char *buffer = malloc(FINAL_HEAP_SIZE);
  assert(buffer);
  // Write value at the end of the buffer to check that any thread can access addresses beyond the initial heap size.
  buffer += FINAL_HEAP_SIZE - 1;
  *buffer = 42;
  return buffer;
}

const char *_Atomic buffer = NULL;

void assert_final_heap_state(bool is_worker) {
  assert(buffer != NULL);
  assert(*buffer == 42);
  js_assert_final_heap_state(is_worker, buffer, FINAL_HEAP_SIZE);
}

void *thread_start(void *arg) {
  assert_initial_heap_state(true);
  // allocate more memory than we currently have, forcing a growth
  buffer = alloc_beyond_initial_heap();
  assert_final_heap_state(true);
  return NULL;
}

int main() {
  assert_initial_heap_state(false);

  pthread_t thr;
  int res = pthread_create(&thr, NULL, thread_start, NULL);
  assert(res == 0);

  while (!buffer);

  assert(*buffer == 42); // should see the value the thread wrote
  assert_final_heap_state(false);

  res = pthread_join(thr, NULL);
  assert(res == 0);

  return 0;
}
