/* go-cgo.c -- SWIG support routines for libgo.

   Copyright 2011 The Go Authors. All rights reserved.
   Use of this source code is governed by a BSD-style
   license that can be found in the LICENSE file.  */

#include "runtime.h"
#include "go-alloc.h"
#include "interface.h"
#include "go-panic.h"
#include "go-type.h"

extern void __go_receive (ChanType *, Hchan *, byte *);

/* Prepare to call from code written in Go to code written in C or
   C++.  This takes the current goroutine out of the Go scheduler, as
   though it were making a system call.  Otherwise the program can
   lock up if the C code goes to sleep on a mutex or for some other
   reason.  This idea is to call this function, then immediately call
   the C/C++ function.  After the C/C++ function returns, call
   syscall_cgocalldone.  The usual Go code would look like

       syscall.Cgocall()
       defer syscall.Cgocalldone()
       cfunction()

   */

/* We let Go code call these via the syscall package.  */
void syscall_cgocall(void) __asm__ (GOSYM_PREFIX "syscall.Cgocall");
void syscall_cgocalldone(void) __asm__ (GOSYM_PREFIX "syscall.CgocallDone");
void syscall_cgocallback(void) __asm__ (GOSYM_PREFIX "syscall.CgocallBack");
void syscall_cgocallbackdone(void) __asm__ (GOSYM_PREFIX "syscall.CgocallBackDone");

void
syscall_cgocall ()
{
  M* m;
  G* g;

  if (runtime_needextram && runtime_cas (&runtime_needextram, 1, 0))
    runtime_newextram ();

  runtime_lockOSThread();

  m = runtime_m ();
  ++m->ncgocall;
  g = runtime_g ();
  ++g->ncgo;
  runtime_entersyscall ();
}

/* Prepare to return to Go code from C/C++ code.  */

void
syscall_cgocalldone ()
{
  G* g;

  g = runtime_g ();
  __go_assert (g != NULL);
  --g->ncgo;
  if (g->ncgo == 0)
    {
      /* We are going back to Go, and we are not in a recursive call.
	 Let the garbage collector clean up any unreferenced
	 memory.  */
      g->cgomal = NULL;
    }

  /* If we are invoked because the C function called _cgo_panic, then
     _cgo_panic will already have exited syscall mode.  */
  if (g->status == Gsyscall)
    runtime_exitsyscall ();

  runtime_unlockOSThread();
}

/* Call back from C/C++ code to Go code.  */

void
syscall_cgocallback ()
{
  M *mp;

  mp = runtime_m ();
  if (mp == NULL)
    {
      runtime_needm ();
      mp = runtime_m ();
      mp->dropextram = true;
    }

  runtime_exitsyscall ();

  if (runtime_g ()->ncgo == 0)
    {
      /* The C call to Go came from a thread not currently running any
	 Go.  In the case of -buildmode=c-archive or c-shared, this
	 call may be coming in before package initialization is
	 complete.  Wait until it is.  */
      __go_receive (NULL, runtime_main_init_done, NULL);
    }

  mp = runtime_m ();
  if (mp->needextram)
    {
      mp->needextram = 0;
      runtime_newextram ();
    }
}

/* Prepare to return to C/C++ code from a callback to Go code.  */

void
syscall_cgocallbackdone ()
{
  M *mp;

  runtime_entersyscall ();
  mp = runtime_m ();
  if (mp->dropextram && runtime_g ()->ncgo == 0)
    {
      mp->dropextram = false;
      runtime_dropm ();
    }
}

/* Allocate memory and save it in a list visible to the Go garbage
   collector.  */

void *
alloc_saved (size_t n)
{
  void *ret;
  G *g;
  CgoMal *c;

  ret = __go_alloc (n);

  g = runtime_g ();
  c = (CgoMal *) __go_alloc (sizeof (CgoMal));
  c->next = g->cgomal;
  c->alloc = ret;
  g->cgomal = c;

  return ret;
}

/* These are routines used by SWIG.  The gc runtime library provides
   the same routines under the same name, though in that case the code
   is required to import runtime/cgo.  */

void *
_cgo_allocate (size_t n)
{
  void *ret;

  runtime_exitsyscall ();
  ret = alloc_saved (n);
  runtime_entersyscall ();
  return ret;
}

extern const struct __go_type_descriptor string_type_descriptor
  __asm__ (GOSYM_PREFIX "__go_tdn_string");

void
_cgo_panic (const char *p)
{
  intgo len;
  unsigned char *data;
  String *ps;
  struct __go_empty_interface e;

  runtime_exitsyscall ();
  len = __builtin_strlen (p);
  data = alloc_saved (len);
  __builtin_memcpy (data, p, len);
  ps = alloc_saved (sizeof *ps);
  ps->str = data;
  ps->len = len;
  e.__type_descriptor = &string_type_descriptor;
  e.__object = ps;

  /* We don't call runtime_entersyscall here, because normally what
     will happen is that we will walk up the stack to a Go deferred
     function that calls recover.  However, this will do the wrong
     thing if this panic is recovered and the stack unwinding is
     caught by a C++ exception handler.  It might be possible to
     handle this by calling runtime_entersyscall in the personality
     function in go-unwind.c.  FIXME.  */

  __go_panic (e);
}

/* Used for _cgo_wait_runtime_init_done.  This is based on code in
   runtime/cgo/gcc_libinit.c in the master library.  */

static pthread_cond_t runtime_init_cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t runtime_init_mu = PTHREAD_MUTEX_INITIALIZER;
static _Bool runtime_init_done;

/* This is called by exported cgo functions to ensure that the runtime
   has been initialized before we enter the function.  This is needed
   when building with -buildmode=c-archive or similar.  */

void
_cgo_wait_runtime_init_done (void)
{
  int err;

  if (__atomic_load_n (&runtime_init_done, __ATOMIC_ACQUIRE))
    return;

  err = pthread_mutex_lock (&runtime_init_mu);
  if (err != 0)
    abort ();
  while (!__atomic_load_n (&runtime_init_done, __ATOMIC_ACQUIRE))
    {
      err = pthread_cond_wait (&runtime_init_cond, &runtime_init_mu);
      if (err != 0)
	abort ();
    }
  err = pthread_mutex_unlock (&runtime_init_mu);
  if (err != 0)
    abort ();
}

/* This is called by runtime_main after the Go runtime is
   initialized.  */

void
_cgo_notify_runtime_init_done (void)
{
  int err;

  err = pthread_mutex_lock (&runtime_init_mu);
  if (err != 0)
    abort ();
  __atomic_store_n (&runtime_init_done, 1, __ATOMIC_RELEASE);
  err = pthread_cond_broadcast (&runtime_init_cond);
  if (err != 0)
    abort ();
  err = pthread_mutex_unlock (&runtime_init_mu);
  if (err != 0)
    abort ();
}

// runtime_iscgo is set to true if some cgo code is linked in.
// This is done by a constructor in the cgo generated code.
_Bool runtime_iscgo;

// runtime_cgoHasExtraM is set on startup when an extra M is created
// for cgo.  The extra M must be created before any C/C++ code calls
// cgocallback.
_Bool runtime_cgoHasExtraM;
