/**
 * \file
 * Expensive asserts used when mono is built with --with-checked-build=yes
 *
 * Author:
 *	Rodrigo Kumpera (kumpera@gmail.com)
 *
 * (C) 2015 Xamarin
 */

#ifndef __CHECKED_BUILD_H__
#define __CHECKED_BUILD_H__

#include <config.h>
#include <mono/utils/atomic.h>
#include <mono/utils/mono-compiler.h>
#include <mono/utils/mono-publib.h>

typedef enum {
	MONO_CHECK_MODE_NONE = 0,
	MONO_CHECK_MODE_GC = 0x1,
	MONO_CHECK_MODE_METADATA = 0x2,
	MONO_CHECK_MODE_THREAD = 0x4,
	MONO_CHECK_MODE_ALL = MONO_CHECK_MODE_GC | MONO_CHECK_MODE_METADATA | MONO_CHECK_MODE_THREAD,
	MONO_CHECK_MODE_UNKNOWN = 0x8
} MonoCheckMode;

G_ENUM_FUNCTIONS (MonoCheckMode)

mono_bool mono_check_mode_enabled (MonoCheckMode query);

// This is for metadata writes which we have chosen not to check at the current time.
// Because in principle this should never happen, we still use a macro so that the exemptions will be easier to find, and remove, later.
// The current reason why this is needed is for pointers to constant strings, which the checker cannot verify yet.
#define CHECKED_METADATA_WRITE_PTR_EXEMPT(ptr, val) do { (ptr) = (val); } while (0)

#ifdef ENABLE_CHECKED_BUILD

#define g_assert_checked g_assert

/*
This can be called by embedders
*/
#define MONO_REQ_API_ENTRYPOINT

/*
The JIT will generate code that will land on this function
*/
#define MONO_REQ_RUNTIME_ENTRYPOINT

#define CHECKED_MONO_INIT() do { checked_build_init (); } while (0)

void checked_build_init (void);

#else

#define g_assert_checked(...)

#define MONO_REQ_API_ENTRYPOINT
#define MONO_REQ_RUNTIME_ENTRYPOINT

#define CHECKED_MONO_INIT()

#endif /* ENABLE_CHECKED_BUILD */

#ifdef ENABLE_CHECKED_BUILD_GC
/*
GC runtime modes rules:

- GC Safe
Can:
Call into foreigh functions.
Call GC Safe or Neutral modes functions.
Read from pinned managed memory.

Cannot:
Touch managed memory (read/write).
Be dettached.

What's good for?
Doing blocking calls.

- GC Unsafe
Can:
Touch managed memory (read/write).
Call GC Unsafe or Neutral modes functions.

Cannot:
Call foreign native code (embedder callbacks, pinvokes, etc)
Call into any Blocking functions/syscalls (mutexes, IO, etc)
Be dettached.

What's good for?
Poking into managed memory.

-- GC Neutral
Can:
Call other GC Neutral mode functions.

Cannot:
Touch managed memory.
Call foreign native code (embedder callbacks, pinvokes, etc)
Call into any Blocking functions/syscalls (mutexes, IO, etc)
Be dettached.

What's good for?
Functions that can be called from both coop or preept modes.

*/

#define MONO_REQ_GC_SAFE_MODE do {	\
	assert_gc_safe_mode (__FILE__, __LINE__);	\
} while (0);

#define MONO_REQ_GC_UNSAFE_MODE do {	\
	assert_gc_unsafe_mode (__FILE__, __LINE__);	\
} while (0);

#define MONO_REQ_GC_NEUTRAL_MODE do {	\
	assert_gc_neutral_mode (__FILE__, __LINE__);	\
} while (0);

/* In a GC critical region, the thread is not allowed to switch to GC safe mode.
 * For example if the thread is about to call a method that will manipulate managed objects.
 * The GC critical region must only occur in unsafe mode.
 */
#define MONO_PREPARE_GC_CRITICAL_REGION					\
	MONO_REQ_GC_UNSAFE_MODE						\
	do {								\
		void* __critical_gc_region_cookie = critical_gc_region_begin()

#define MONO_FINISH_GC_CRITICAL_REGION			\
		critical_gc_region_end(__critical_gc_region_cookie);	\
	} while(0)

/* Verify that the thread is not currently in a GC critical region. */
#define MONO_REQ_GC_NOT_CRITICAL do {			\
		assert_not_in_gc_critical_region();	\
	} while(0)

/* Verify that the thread is currently in a GC critical region. */
#define MONO_REQ_GC_CRITICAL do {			\
		assert_in_gc_critical_region();	\
	} while(0)

void assert_gc_safe_mode (const char *file, int lineno);
void assert_gc_unsafe_mode (const char *file, int lineno);
void assert_gc_neutral_mode (const char *file, int lineno);

void* critical_gc_region_begin(void);
void critical_gc_region_end(void* token);
void assert_not_in_gc_critical_region(void);
void assert_in_gc_critical_region (void);

#else

#define MONO_REQ_GC_SAFE_MODE
#define MONO_REQ_GC_UNSAFE_MODE
#define MONO_REQ_GC_NEUTRAL_MODE

#define MONO_PREPARE_GC_CRITICAL_REGION
#define MONO_FINISH_GC_CRITICAL_REGION

#define MONO_REQ_GC_NOT_CRITICAL
#define MONO_REQ_GC_CRITICAL

#endif /* defined(ENABLE_CHECKED_BUILD_GC) */

#ifdef ENABLE_CHECKED_BUILD_METADATA

// Use when writing a pointer from one image or imageset to another.
#define CHECKED_METADATA_WRITE_PTR(ptr, val) do {    \
    check_metadata_store (&(ptr), (val));    \
    (ptr) = (val);    \
} while (0);

// Use when writing a pointer from an image or imageset to itself.
#define CHECKED_METADATA_WRITE_PTR_LOCAL(ptr, val) do {    \
    check_metadata_store_local (&(ptr), (val));    \
    (ptr) = (val);    \
} while (0);

// Use when writing a pointer from one image or imageset to another (atomic version).
#define CHECKED_METADATA_WRITE_PTR_ATOMIC(ptr, val) do {    \
    check_metadata_store (&(ptr), (val));    \
    mono_atomic_store_release (&(ptr), (val));    \
} while (0);

void check_metadata_store(void *from, void *to);
void check_metadata_store_local(void *from, void *to);

#define CHECKED_METADATA_STORE(ptr, val) check_metadata_store ((ptr), (val))
#define CHECKED_METADATA_STORE_LOCAL(ptr, val) check_metadata_store_local ((ptr), (val))

#else

#define CHECKED_METADATA_WRITE_PTR(ptr, val) do { (ptr) = (val); } while (0)
#define CHECKED_METADATA_WRITE_PTR_LOCAL(ptr, val) do { (ptr) = (val); } while (0)
#define CHECKED_METADATA_WRITE_PTR_ATOMIC(ptr, val) do { mono_atomic_store_release (&(ptr), (val)); } while (0)

#define CHECKED_METADATA_STORE(ptr, val) do { (ptr); (val); } while (0)
#define CHECKED_METADATA_STORE_LOCAL(ptr, val) do { (ptr); (val); } while (0)

#endif /* defined(ENABLE_CHECKED_BUILD_METADATA) */

#ifdef ENABLE_CHECKED_BUILD_THREAD

#define CHECKED_BUILD_THREAD_TRANSITION(transition, info, from_state, suspend_count, next_state, suspend_count_delta) do {	\
		checked_build_thread_transition (transition, info, from_state, suspend_count, next_state, suspend_count_delta, TRUE); \
} while (0)

#define CHECKED_BUILD_THREAD_TRANSITION_NOBT(transition, info, from_state, suspend_count, next_state, suspend_count_delta) do {	\
		checked_build_thread_transition (transition, info, from_state, suspend_count, next_state, suspend_count_delta, FALSE); \
} while (0)

void checked_build_thread_transition(const char *transition, void *info, int from_state, int suspend_count, int next_state, int suspend_count_delta, gboolean capture_backtrace);

G_GNUC_NORETURN MONO_ATTR_FORMAT_PRINTF(1,2) void mono_fatal_with_history(const char *msg, ...);

#else

#define CHECKED_BUILD_THREAD_TRANSITION(transition, info, from_state, suspend_count, next_state, suspend_count_delta)

#define CHECKED_BUILD_THREAD_TRANSITION_NOBT(transition, info, from_state, suspend_count, next_state, suspend_count_delta)

#define mono_fatal_with_history g_error

#endif /* defined(ENABLE_CHECKED_BUILD_THREAD) */

#endif /* __CHECKED_BUILD_H__ */
