// Will be included once by runtime/common_session_state.h, which is included
// once by translate.cxx c_unparser::emit_common_header ().


// Forward declarations for things in runtime/dyninst/shm.c
static void *_stp_shm_base;
static void *_stp_shm_alloc(size_t size);

#include "session_attributes.h"
#include "transport.h"

// Global state shared throughout the module
struct stp_runtime_session {
	atomic_t _session_state;

	atomic_t _error_count;
	atomic_t _skipped_count;
	atomic_t _skipped_count_lowstack;
	atomic_t _skipped_count_reentrant;
	atomic_t _skipped_count_uprobe_reg;
	atomic_t _skipped_count_uprobe_unreg;

        unsigned long _hash_seed;

	struct _stp_session_attributes _session_attributes;

#ifdef STP_ALIBI
	atomic_t _probe_alibi[STP_PROBE_COUNT];
#endif

#ifdef STP_TIMING
	offptr_t _probe_timing[STP_PROBE_COUNT]; // offptr<Stat>
#endif

	struct stp_globals _global;

	struct _stp_transport_session_data _transport_data;

	// NB: the context includes a number of pointers, which wouldn't be
	// kosher for shared memory, but it's ok as long as they're only set
	// and dereferenced within each separate handler invocation.
	struct context _context[]; // variably-sized to cpu count
};

static inline struct stp_runtime_session* _stp_session(void)
{
	// Since the session is always the first thing allocated, it lives
	// directly at the start of shared memory.
	// NB: We always reference the main shm pointer, rather than our own
	// local copy, because reallocations may cause it to move dynamically.
	return _stp_shm_base;
}


#define GET_SESSION_ATOMIC(name)			\
	static inline atomic_t *name(void) {		\
		if (unlikely(_stp_session() == NULL))	\
			return NULL;			\
		return &_stp_session()->_##name;	\
	}

GET_SESSION_ATOMIC(session_state);

GET_SESSION_ATOMIC(error_count);
GET_SESSION_ATOMIC(skipped_count);
GET_SESSION_ATOMIC(skipped_count_lowstack);
GET_SESSION_ATOMIC(skipped_count_reentrant);
GET_SESSION_ATOMIC(skipped_count_uprobe_reg);
GET_SESSION_ATOMIC(skipped_count_uprobe_unreg);

#undef GET_SESSION_ATOMIC


static inline unsigned long _stap_hash_seed()
{
	if (likely(_stp_session()))
		return _stp_session()->_hash_seed;
	return 0;
}


static inline struct _stp_session_attributes *stp_session_attributes(void)
{
	if (likely(_stp_session()))
		return &_stp_session()->_session_attributes;
	return NULL;
}


#ifdef STP_ALIBI
static inline atomic_t *probe_alibi(size_t index)
{
	// Do some simple bounds-checking.  Translator-generated code
	// should never get this wrong, but better to be safe.
	index = clamp_t(size_t, index, 0, STP_PROBE_COUNT - 1);
	if (likely(_stp_session()))
		return &_stp_session()->_probe_alibi[index];
	return NULL;
}
#endif

#ifdef STP_TIMING
static inline Stat probe_timing(size_t index)
{
	// Do some simple bounds-checking.  Translator-generated code
	// should never get this wrong, but better to be safe.
	index = clamp_t(size_t, index, 0, STP_PROBE_COUNT - 1);
	if (likely(_stp_session()))
		return offptr_get(&_stp_session()->_probe_timing[index]);
	return NULL;
}
#endif


static inline struct context* stp_session_context(size_t index)
{
	// Do some simple bounds-checking.  Translator-generated code
	// should never get this wrong, but better to be safe.
	index = clamp_t(size_t, index, 0, _stp_runtime_num_contexts - 1);
	if (likely(_stp_session()))
		return &_stp_session()->_context[index];
	return NULL;
}


static inline struct _stp_transport_session_data *stp_transport_data(void)
{
	if (_stp_session())
		return &_stp_session()->_transport_data;
	return NULL;
}

#define _global_raw(name)	(_stp_session()->_global.name)
#define _global_type(name)	typeof(_global_raw(name))
#define _global_cast_type(name)	_global_type(name ## _typed)
#define _global_offptr_p(name)	\
	__builtin_types_compatible_p(_global_type(name), offptr_t)
#define _global_compat_p(name, value)	\
	__builtin_types_compatible_p(_global_cast_type(name), typeof(value))

// NB: The (offptr_t*) cast is unfortunately necessary for the first expression
// to pass syntax on long/string types, but __builtin_choose_expr will still
// direct them to the second expression due to the _global_offptr_p check.
#define global(name)							\
	__builtin_choose_expr(_global_offptr_p(name),			\
		((_global_cast_type(name))				\
			offptr_get((offptr_t*)&_global_raw(name))),	\
		_global_raw(name))

// NB: This is only used for pointer->offptr assignments, for stats and maps.
// Unlike global() which just returns string/long types by value, here we'd
// have to encode their separate assignment schemes, in a way that stays
// syntax-compatible with all others.  The translator can do that simpler.
// NB: Evaluate the value *before* the offptr destination is computed, in
// case the value's allocation causes shm to move.
#define global_set(name, value)	({					\
	typeof(value) __value = (value);				\
	offptr_set(&_global_raw(name),					\
		__builtin_choose_expr(_global_compat_p(name, __value),	\
			__value, (void)0));				\
	__value; })

#define global_lock(name)	(&_global_raw(name ## _lock))
#define global_lock_init(name)	\
	stp_pthread_rwlock_init_shared(global_lock(name))

#ifdef STP_TIMING
#define global_skipped(name)	(&_global_raw(name ## _lock_skip_count))
#endif


static int stp_session_init(void)
{
	size_t i;

	// Reserve space for the session at the beginning of shared memory.
	size_t session_size = sizeof(struct stp_runtime_session)
		+ sizeof(struct context) * _stp_runtime_num_contexts;
	void *session = _stp_shm_zalloc(session_size);
	if (!session)
		return -ENOMEM;

	// If we weren't the very first thing allocated, then something is wrong!
	if (session != _stp_shm_base)
		return -EINVAL;

	atomic_set(session_state(), STAP_SESSION_STARTING);

	atomic_set(error_count(), 0);
	atomic_set(skipped_count(), 0);
	atomic_set(skipped_count_lowstack(), 0);
	atomic_set(skipped_count_reentrant(), 0);
	atomic_set(skipped_count_uprobe_reg(), 0);
	atomic_set(skipped_count_uprobe_unreg(), 0);

	_stp_session()->_hash_seed = _stp_random_u ((unsigned long)-1);

	stp_session_attributes_init();

#ifdef STP_ALIBI
	// Initialize all the alibi counters
	for (i = 0; i < STP_PROBE_COUNT; ++i)
		atomic_set(probe_alibi(i), 0);
#endif

#ifdef STP_TIMING
	// Initialize each Stat used for timing information
	for (i = 0; i < STP_PROBE_COUNT; ++i) {
		// NB: we don't check for null return here, but instead at
		// passage to probe handlers and at final printing.
		Stat st = _stp_stat_init(HIST_NONE);

		// NB: allocate first, then dereference the session after, in case
		// allocation-resizing causes the whole thing to move around.
		offptr_set(&_stp_session()->_probe_timing[i], st);
	}
#endif

	return _stp_dyninst_transport_session_init();
}

static int stp_session_init_finished(void)
{
	return stp_dyninst_session_init_finished();
}
