/* Interface to external module (*Serv) symbols for database modules.
 *
 * IRC Services is copyright (c) 1996-2009 Andrew Church.
 *     E-mail: <achurch@achurch.org>
 * Parts written by Andrew Kempe and others.
 * This program is free but copyrighted software; see the file COPYING for
 * details.
 */

#include "services.h"
#include "modules.h"

#define IN_EXTSYMS_C
#include "extsyms.h"

/*************************************************************************/

#ifdef __INTEL_COMPILER  /* icc hides the symbols from the inline asm */
# define static
#endif
static Module *module_nickserv;
static Module *module_chanserv;
static Module *module_memoserv;
static Module *module_operserv;
static Module *module_statserv;
#ifdef __INTEL_COMPILER
# undef static
#endif

static const char *this_module_name = "(unknown-module)";

/*************************************************************************/

static void fatal_no_symbol(const char *symbol)
{
    fatal("%s: undefined symbol `%s'", this_module_name, symbol);
}

/*************************************************************************/
/*************************************************************************/

#if defined(GCC3_HACK)

# if defined(__sparc__)

#  define IMPORT_FUNC(modulename, module, func)				\
static void __dblocal_##func##_stub0(void) {				\
    void *ptr = NULL;							\
    if (!module)							\
	module = find_module(modulename);				\
    if (module)								\
	ptr = get_module_symbol(module, #func);				\
    if (!ptr)								\
	fatal_no_symbol(#func);						\
    __dblocal_##func = ptr;						\
}									\
static void *__dblocal_##func##_stub1(void) {				\
    __dblocal_##func##_stub0();						\
    __builtin_return(__builtin_apply((void *)__dblocal_##func,		\
		     __builtin_apply_args(), 64));			\
}									\
static void __dblocal_##func##_stub(void) {				\
    (void) __builtin_apply((void *)__dblocal_##func##_stub1,		\
                           __builtin_apply_args(), 64);			\
    asm("ld [%sp-128],%i0");						\
}									\
typeof(func) *__dblocal_##func = (typeof(func) *) __dblocal_##func##_stub;

#elif defined(__POWERPC__)

#  define IMPORT_FUNC(modulename, module, func)				\
static void __dblocal_##func##_stub0(void) {				\
    void *ptr = NULL;							\
    if (!module)							\
	module = find_module(modulename);				\
    if (module)								\
	ptr = get_module_symbol(module, #func);				\
    if (!ptr)								\
	fatal_no_symbol(#func);						\
    __dblocal_##func = ptr;						\
}									\
static void __dblocal_##func##_stub(void) {				\
    asm("lwz r0,0(r1)\n							\
         stwu r0,-104(r1)\n						\
         stw r3,64(r1)\n						\
         stw r4,68(r1)\n						\
         stw r5,72(r1)\n						\
         stw r6,76(r1)\n						\
         stw r7,80(r1)\n						\
         stw r8,84(r1)\n						\
         stw r9,88(r1)\n						\
         stw r10,92(r1)\n						\
         mflr r0\n							\
         stw r0,100(r1)");						\
    asm("mtctr %0\n							\
         bctrl\n							\
         lwz r10,92(r1)" : : "r" (__dblocal_##func##_stub0));		\
    asm("stw %0,96(r1)" : : "r" (__dblocal_##func));			\
    asm("lwz r3,64(r1)\n						\
         lwz r4,68(r1)\n						\
         lwz r5,72(r1)\n						\
         lwz r6,76(r1)\n						\
         lwz r7,80(r1)\n						\
         lwz r8,84(r1)\n						\
         lwz r9,88(r1)\n						\
         lwz r10,92(r1)\n						\
         lwz r11,96(r1)\n						\
         mtctr r11\n							\
         bctrl\n							\
         lwz r0,100(r1)\n						\
         mtlr r0\n							\
         mr r0,r3");							\
}									\
typeof(func) *__dblocal_##func = (typeof(func) *) __dblocal_##func##_stub;

#else  /* not SPARC and not PowerPC... must be our good old friend */

#  define IMPORT_FUNC(modulename, module, func)				\
static void __dblocal_##func##_stub0(void) {				\
    void *ptr = NULL;							\
    if (!module)							\
	module = find_module(modulename);				\
    if (module)								\
	ptr = get_module_symbol(module, #func);				\
    if (!ptr)								\
	fatal_no_symbol(#func);						\
    __dblocal_##func = ptr;						\
}									\
static void *__dblocal_##func##_stub(void) {				\
    __dblocal_##func##_stub0();						\
    __builtin_return(__builtin_apply((void *)__dblocal_##func,		\
		     __builtin_apply_args(), 64));			\
}									\
typeof(func) *__dblocal_##func = (typeof(func) *) __dblocal_##func##_stub;

#endif /* to SPARC or not to SPARC */

#elif defined(__GNUC__)

#define IMPORT_FUNC(modulename, module, func)				\
static void *__dblocal_##func##_stub(void) {				\
    void *ptr = NULL;							\
    if (!module)							\
	module = find_module(modulename);				\
    if (module)								\
	ptr = get_module_symbol(module, #func);				\
    if (!ptr)								\
	fatal_no_symbol(#func);						\
    __dblocal_##func = ptr;						\
    __builtin_return(__builtin_apply(ptr, __builtin_apply_args(), 64));	\
}									\
typeof(func) *__dblocal_##func = (typeof(func) *) __dblocal_##func##_stub;

#elif defined(__INTEL_COMPILER)

volatile void *_fatal_no_symbol = (void *)fatal_no_symbol;

#define IMPORT_FUNC(modulename, module, func)	\
static void __dblocal_##func##_stub(void) {	\
asm("	movl	"#module",%eax;			\
	testl	%eax,%eax;			\
	jnz	1f;				\
	leal	8f,%eax;			\
	pushl	%eax;				\
	call	find_module;			\
	leal	4(%esp),%esp;			\
	movl	%eax,"#module";			\
1:	testl	%eax,%eax;			\
	jz	2f;				\
	leal	9f,%edx;			\
	pushl	%edx;				\
	pushl	%eax;				\
	call	get_module_symbol;		\
	leal	8(%esp),%esp;			\
2:	testl	%eax,%eax;			\
	jnz	3f;				\
	leal	9f,%eax;			\
	pushl	%eax;				\
	call	*_fatal_no_symbol;		\
	leal	4(%esp),%esp;			\
3:	movl	%eax,__dblocal_"#func";		\
	movl	%ebp,%esp;			\
	popl	%ebp;				\
	jmp	*%eax;				\
8:	.ascii	\"" modulename "\\0\";		\
9:	.ascii	\""#func"\\0\"			\
");						\
}						\
typeof(func) *__dblocal_##func = (typeof(func) *) __dblocal_##func##_stub;

#else

#error IMPORT_FUNC not implemented for this compiler

#endif  /* GCC3_HACK/etc */


#define IMPORT_VAR(modulename, module, var)				\
static typeof(var) *__dblocal_##var##_ptr;				\
typeof(var) __dblocal_get_##var(void) {					\
    if (!__dblocal_##var##_ptr) {					\
	if (!module)							\
	    module = find_module(modulename);				\
	if (module)							\
	    __dblocal_##var##_ptr = get_module_symbol(module, #var);	\
	if (!__dblocal_##var##_ptr)					\
	    fatal_no_symbol(#var);					\
    }									\
    return *__dblocal_##var;						\
}


#define IMPORT_VAR_MAYBE(modulename, module, var, default)		\
static typeof(var) *__dblocal_##var##_ptr;				\
typeof(var) __dblocal_get_##var(void) {					\
    if (!__dblocal_##var##_ptr) {					\
	if (!module)							\
	    module = find_module(modulename);				\
	if (module)							\
	    __dblocal_##var##_ptr = get_module_symbol(module, #var);	\
    }									\
    return __dblocal_##var##_ptr ? *__dblocal_##var##_ptr : default;	\
}

/*************************************************************************/

IMPORT_VAR_MAYBE("chanserv/main", module_chanserv, CSMaxReg, CHANMAX_DEFAULT);
IMPORT_VAR_MAYBE("memoserv/main", module_memoserv, MSMaxMemos,MEMOMAX_DEFAULT);

IMPORT_FUNC("nickserv/main", module_nickserv, _get_ngi);
IMPORT_FUNC("nickserv/main", module_nickserv, _get_ngi_id);
IMPORT_FUNC("nickserv/main", module_nickserv, check_expire_nick);
IMPORT_FUNC("chanserv/main", module_chanserv, check_expire_channel);
IMPORT_FUNC("chanserv/main", module_chanserv, reset_levels);
IMPORT_FUNC("operserv/main", module_operserv, check_expire_maskdata);
IMPORT_FUNC("statserv/main", module_statserv, new_serverstats);
IMPORT_FUNC("statserv/main", module_statserv, free_serverstats);

/*************************************************************************/
/*************************************************************************/

static int do_unload_module(Module *mod)
{
    if (mod == module_nickserv) {
	module_nickserv = NULL;
	__dblocal__get_ngi = (void *) __dblocal__get_ngi_stub;
	__dblocal__get_ngi_id = (void *) __dblocal__get_ngi_id_stub;
    } else if (mod == module_chanserv) {
	module_chanserv = NULL;
	__dblocal_CSMaxReg_ptr = NULL;
	__dblocal_reset_levels = (void *) __dblocal_reset_levels_stub;
    } else if (mod == module_memoserv) {
	module_memoserv = NULL;
	__dblocal_MSMaxMemos_ptr = NULL;
    } else if (mod == module_statserv) {
	module_statserv = NULL;
	__dblocal_new_serverstats = (void *) __dblocal_new_serverstats_stub;
	__dblocal_free_serverstats = (void *) __dblocal_free_serverstats_stub;
    }
    return 0;
}

/*************************************************************************/

/* `name' is the name of the calling module (used for errors) */

int init_extsyms(const char *name)
{
    if (!add_callback(NULL, "unload module", do_unload_module))
	return 0;
    this_module_name = name;
    return 1;
}

/*************************************************************************/

void exit_extsyms()
{
    remove_callback(NULL, "unload module", do_unload_module);
}

/*************************************************************************/
