The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"

// All the behaviroal definitions begin and end here.  For more
// infomration on these definitions, please review the POD
// #define TRACE_LOG
#define MINDFUL_REFS
//#define MINDFUL_CIR
#define ALLOW_HOOKS

// Define some logging functions
#ifdef TRACE_LOG
#define xT()          printf( "%s:%d: ", __FUNCTION__, __LINE__ )
#define xPNL()        printf( "\n" )
#define TRACE(m)      xT() && printf m
#define SV_TRACE(s,c) xT() && printf( "$src = %d(0x%x); $cln = %c(0x%x)", SvREFCNT( s ), s, SvREFCNT( c ), c  ) && xPNL()
#else
#define TRACE(m)
#define SV_TRACE(s,c)
#endif

// The SV_(TRIAGE|STORE)? macros are used inline to determine if/when/how
// we should store the current { source => clone } in order to sustain
// circular and internal structure references
#ifdef MINDFUL_REFS
#define SV_STORE(s,c) do {\
	if ( ! hv_store( sv_cache, (char*)s, PTRSIZE, SvREFCNT_inc( c ), 0 ) )\
		warn( "Warning: Invalid assignment of value to HASH key!" );\
} while( 0 )\

#define SV_TRIAGE(s,c) do{\
	if ( KEEP_REF() && SvREFCNT( s ) > 1 )\
		SV_STORE( s, c );\
} while( 0 )\

#else
#define SV_STORE(s,c)
#define SV_TRIAGE(s,c)
#endif

// This macro will be for the hooking of Clone-type objects that are
// being cloned.  Using configuration variables defined in the Perl
// package, we can turn this macro on and/or off programatically.
// see POD for more details
bool    watch_hooks;
#ifdef  ALLOW_HOOKS
#define SV_HOOK_OBJECT(s,c) do{\
	sv_bless( c, SvSTASH( SvRV( s ) ) );\
	if ( watch_hooks ) {\
		GV * clone_hook = gv_fetchmethod_autoload( 	SvSTASH( SvRV( source ) ), "CLONEFAST_clone", FALSE );\
		if ( clone_hook ) {\
			dSP;\
			int count;\
			ENTER;\
			SAVETMPS;\
			PUSHMARK(SP);\
			XPUSHs( sv_2mortal( c ) );\
			XPUSHs( sv_2mortal( s ) );\
			PUTBACK;\
			count = perl_call_sv( (SV*)clone_hook, G_SCALAR );\
			TRACE( ( "Return of %d returned from hook\n", count ) );\
			SPAGAIN;\
			TRACE( ( "HookING $source=0x%x, $clone=0x%x\n", s, c ) );\
			if ( SvTRUE( ERRSV ) ) {\
				STRLEN n_a;\
				printf ("Something went impossibly wrong: %s\n", SvPV(ERRSV, n_a));\
				POPs;\
			}\
			else if ( count ){\
				c = SvREFCNT_inc( POPs );\
				s = SvREFCNT_inc( s );\
			}\
			else\
				croak( "CLONEFAST_store did not return anticipated value; expected 1 return, got %d\n", count );\
			if ( ! SvROK( c ) )\
				croak( "CLONEFAST_store expected reference as return, got %d\n", SvTYPE( c ) );\
			TRACE( ( "HookED $source=0x%x, $clone=0x%x\n", s, c ) );\
			PUTBACK;\
			FREETMPS;\
			LEAVE;\
		}\
	}\
} while( 0 )\

#else
#define SV_HOOK_OBJECT(s,c) do{\
	sv_bless( c, SvSTASH( SvRV( s ) ) );\
} while( 0 )\

#endif

// Used for the manipulaton of internal referencing
bool break_refs;
#define KEEP_REF()     ( ! break_refs ) 

// Used for the toggling of circular reference checks
bool ignore_circular;
#define CHECK_CIRCLE() ( ! ignore_circular )

// General constants we can use
#define MAGIC_QR      'r'
#define MAGIC_TAINT   't'
#define MAGIC_BACKREF '<'
#define MAGIC_USERDEF '~'
#define MAGIC_ARYLEN  '@'

// Primary and recursive cloning functions
static SV * sv_clone( SV *         );
static SV * hv_clone( HV *, HV *   );
static SV * av_clone( AV *, AV *   );
static SV * mg_clone( SV *         );
static SV * sv_seen ( SV *         );

// Generalized and listed cloning functions
static SV * clone_sv( SV * );
static SV * clone_rv( SV * );
static SV * clone_av( SV * );
static SV * clone_hv( SV * );
static SV * no_clone( SV * );

// Dynamic dispatching table, mapping the particular
// data type to the enumerated-ish cloning function
typedef SV * ( * sv_clone_t )( SV * source );
static sv_clone_t sv_clone_table[] = {
	(sv_clone_t)clone_sv,   // SVt_NULL
#if PERL_VERSION >= 9
	(sv_clone_t)no_clone,   // SVt_BIND
#endif
	(sv_clone_t)clone_sv,   // SVt_IV
	(sv_clone_t)clone_sv,   // SVt_NV
	(sv_clone_t)clone_rv,   // SVt_RV
	(sv_clone_t)clone_sv,   // SVt_PV
	(sv_clone_t)clone_sv,   // SVt_PVIV
	(sv_clone_t)clone_sv,   // SVt_PVNV
	(sv_clone_t)clone_sv,   // SVt_PVMG
#if PERL_VERSION <= 8
	(sv_clone_t)no_clone,   // SVt_PVBM
#endif
#if PERL_VERSION >= 9
	(sv_clone_t)no_clone,   // SVt_GV
#endif
	(sv_clone_t)no_clone,   // SVt_PVLV
	(sv_clone_t)clone_av,   // SVt_PVAV
	(sv_clone_t)clone_hv,   // SVt_PVHV
	(sv_clone_t)no_clone,   // SVt_CV
#if PERL_VERSION <= 8
	(sv_clone_t)no_clone,   // SVt_GV	
#endif
	(sv_clone_t)no_clone,   // SVt_FM
	(sv_clone_t)no_clone,   // SVt_IO
};

// Simple accessor into the sv_clone[] table //
#define SV_CLONE(x) (*sv_clone_table[x])

// Used to determine internal structure references
HV * sv_cache;

// Used to better track circular references
static bool sv_is_circular   ( SV * );
static bool sv_deeply_circular( SV * );
HV * sv_circle;
I32  sv_depth;

// Used to programatically determine what the heck to do
// with circular references
static SV * build_circular_return( SV *, I32 );

static SV * sv_clone( SV * source ) {
	SV * clone;

	if ( SvREFCNT( source ) > 1 ) {
#ifdef MINDFUL_CIR
		if ( CHECK_CIRCLE() && sv_is_circular( source ) )
			 return build_circular_return( source, (I32)SvIVX(perl_get_sv( "Clone::Fast::CIRCULAR_ACTION", TRUE ) ) );
#endif
#ifdef MINDFUL_REFS
		if ( KEEP_REF() && ( clone = sv_seen( source ) ) )
			return clone;
#endif
	}
	
	// Will make a single call to an indexed list of possible
	// cloning functions.  This should allow for a much more
	// liniar performance implications
	clone = ( ( SvMAGICAL( source ) ) ? mg_clone( source ) : SV_CLONE( SvTYPE( source ) )( source ) );
	sv_depth++;
	
	SV_TRACE( source, clone );
	return clone;
}

static SV * build_circular_return( SV * source, I32 action ) {
	SV * clone;

	TRACE( ( "Cir => 0x%x; Act = %d\n", source, action ) );

	// Currently supported options.
	// 0b000  ( 0 ) => Will continue the circular reference (default)
	// 0b001  ( 1 ) => Will return an incremented version of the source
	// 0b010  ( 2 ) => Will undef the value
	// 0b100  ( 4 ) => Will warn about the circular reference, acting as 0b000
	switch( action ) {
		case 0:
			if ( ( clone = sv_seen( source ) ) )
				return clone;
			return build_circular_return( source, 1 );
			break;
		case 1:
			return SvREFCNT_inc( source );
			break;
		case 2:
			return &PL_sv_undef;
			break;
		case 4:
			warn( "Warning: Circular reference detected at 0x%x", source );
			return build_circular_return( source, 0 );
			break;
		default:
			warn( "Invalid CIRCULAR_ACTION, using default\n" );
			return build_circular_return( source, 0 );
			break;
	}

	// Should NEVER get here with the switch(){default:};
	croak( "Unexpected behavior when building circular return" );
}

static SV * clone_hv( SV * source ) {
	HV * clone = newHV();
	
	// We can store off the new clone pointer now that we have it
	SV_TRIAGE( source, (SV*)clone );
		
	// Clone away
	return hv_clone( (HV*)source, clone );
}

static SV * clone_av( SV * source ) {
	AV * clone = newAV();

	// We can store off the new clone pointer now that we have it
	SV_TRIAGE( source, (SV*)clone );
	
	// Clone away
	return av_clone( (AV*)source, clone );
}

static SV * no_clone( SV * source ) {
	SV * clone = SvREFCNT_inc( source );
	
	TRACE( ( "Returning incrementned source\n" ) );
	
	// We can store off the new clone pointer now that we have it
	SV_TRIAGE( source, clone );
	
	return clone;
}

static SV * clone_rv( SV * source ) {
	SV *  clone;
	
	TRACE( ( "Ripping reference from source\n" ) );

	if ( ! SvROK( source ) ) {
		clone = SvREFCNT_inc( source );
		SV_TRIAGE( source, clone );
		return clone;
	}
	else {
		clone = newSV(0);
		SvUPGRADE( clone, SVt_RV );
		SV_TRIAGE( source, clone );
	}

	SvROK_on( clone );
	SvRV( clone ) = sv_clone( SvRV( source ) );
	
	if ( sv_isobject( source ) )
		SV_HOOK_OBJECT( source, clone );
	
	return clone;
}

static SV * clone_sv( SV * source ) {
	SV * clone;
	TRACE( ( "Cloning SVsv\n" ) );
	
	if ( SvROK( source ) )
		clone = clone_rv( source );
	else {
		clone = newSVsv( source );
		SV_TRIAGE( source, clone );
	}
	
	return clone;
}

static SV * hv_clone( HV * source, HV * clone ) {
	HE * iter = NULL;	

	TRACE( ( "Cloning HASH\n" ) );

	hv_iterinit( source );
	while ( iter = hv_iternext( source ) ) {
		SV * key = hv_iterkeysv( iter );
		hv_store_ent( clone, key, sv_clone( hv_iterval( source, iter ) ), 0 );
	}

	return (SV*)clone;
}

static SV * av_clone ( AV * source, AV * clone ) {
	int i;
	SV ** t_svp;

	TRACE( ( "Cloning ARRAY\n" ) );
	
	/* 
	 * Need to make sure the clone length is the same
	 * size as the source length; let Perl handle it
	 */
	if ( av_len( clone ) < av_len( source ) )
		av_extend( clone, av_len( source ) );

	for ( i = 0; i <= av_len( source ); i++ ) {
		t_svp = av_fetch( source, i, 0 );
		if ( t_svp )
			av_store( clone, i, sv_clone( *t_svp ) );
	}

	return (SV*)clone;
}

static SV * mg_clone( SV * source ) {
	SV    * clone;
	MAGIC * mg;
	bool    mg_flg = FALSE;

	//
	// This is a little different than the normal dispatching
	// algorithms, however is pretty close to the same to.
	//
	// TBD: This needs some serious clean up work.  Two case
	//      blocks and a conditional tree make for some slow
	//      copying of magic crap.  Though it seems to work ;)
	//
	switch( SvTYPE( source ) ) {
		case SVt_RV:
			clone = newSV(0);
			sv_upgrade( clone, 3 );
		case SVt_PVAV:
			clone = (SV*)newAV();
			break;
		case SVt_PVHV: 
			clone = (SV*)newHV();
			break;
		default:
			clone = source;
	}
	clone = SvREFCNT_inc( clone ); // Boink!

	for ( mg = SvMAGIC( source ); mg; mg = mg->mg_moremagic ) {
		SV    * obj = Nullsv;
		
		// How magic is it?	
		switch (mg->mg_type) {
			case MAGIC_QR:
				obj = mg->mg_obj;
				break;
			case MAGIC_TAINT:
				continue;
				break;
			case MAGIC_BACKREF:
				continue;
				break;
			case MAGIC_USERDEF:
			case MAGIC_ARYLEN:
				obj = mg->mg_obj;
				break;
			default:
				// TBD: Do we need to store this now, or will sv_clone() take
				//      care of it??
				if ( mg->mg_obj ) {
					obj = sv_clone( mg->mg_obj );
				}
		}
		mg_flg = TRUE;

		// Magicasize it!
		sv_magic( clone, obj, mg->mg_type, mg->mg_ptr, mg->mg_len );
	}

	if ( mg = mg_find( clone, MAGIC_QR ) )
		mg->mg_virtual = (MGVTBL*)NULL;

	// Now we can watch for the monitor flag
	if ( ! mg_flg ) {
		if ( SvTYPE( source ) == SVt_PVHV )
			clone = hv_clone( (HV*)source, (HV*)clone );
		else if ( SvTYPE( source ) == SVt_PVAV )
			clone = av_clone( (AV*)source, (AV*)clone );
		else if ( SvROK( source ) ) {
			SvROK_on( clone );
			SvRV( clone ) = sv_clone( SvRV( source ) );
			if ( sv_isobject( source ) )
				SV_HOOK_OBJECT( source, clone );
		}
	}
	
	return clone;
}

static SV * sv_seen ( SV * source ) {
	SV ** seen;

	SV_TRACE( source, source );
	
	if ( seen = hv_fetch( sv_cache, (char*)source, PTRSIZE, 0 ) )
		return SvREFCNT_inc( *seen ); 

	return NULL;
}

static bool sv_is_circular( SV * source ) {
	SV ** sv_monitor;
	SV ** sv_elem;
	AV *  av_monitor;
	int i;
	
	TRACE( ( "Testing for circularity at source 0x%x\n", source ) );

	// If the source hasn't been here yet, then initiate the HV key with the source
	if ( ! hv_exists( sv_circle, (char*)source, PTRSIZE ) ) {
		TRACE( ( "Source, 0x%x, not yet watched\n", source ) );
		av_monitor = newAV();
		av_push( av_monitor, SvREFCNT_inc( source ) );
		hv_store( sv_circle, (char*)source, PTRSIZE, (SV*)av_monitor, 0 );
		return FALSE;
	}
	else if ( ( sv_monitor = hv_fetch( sv_circle, (char*)source, PTRSIZE, 0 ) ) ) {
		TRACE( ( "Source, 0x%x, being watched...\n", source ) );
		av_monitor = (AV*)*sv_monitor;
		for ( i = 0; i <= av_len( av_monitor ); i++ ) {
			TRACE( ( "Source, 0x%x, against 0x%x\n", source, *sv_elem ) );
			sv_elem = av_fetch( av_monitor, i, 0 );
			if ( ( source == *sv_elem ) )
				return TRUE;
		}
		TRACE( ( "Source, 0x%x, not within ones self; continuing\n", source ) );
		av_push( av_monitor, SvREFCNT_inc( source ) );
		return FALSE;
	}
	else
		croak( "Circular integrity engine failed critically!\n" );
}

static bool sv_deeply_circular( SV * source ) {
	int i;
	SV   ** av_elem;
	HE   *  hv_iter;
	SV   *  hv_val;

	TRACE( ( "0x%x => %d (depth = %d)\n", source, SvTYPE( source ), sv_depth ) );
	
	if ( sv_is_circular( source ) )
		return TRUE;

	switch( SvTYPE( source ) ) {
		case  SVt_RV:
			return sv_deeply_circular( SvRV( source ) );
			break;
		case SVt_PVAV:
			for ( i = 0; i <= av_len( (AV*)source ); i++ ) {
				av_elem = av_fetch( (AV*)source, i, 0 );
				if ( av_elem && sv_deeply_circular( *av_elem ) )
					return TRUE;	
			}
			break;
		case SVt_PVHV:
			hv_iterinit( (HV*)source );
			while ( hv_iter = hv_iternext( (HV*)source ) ) {
				hv_val = hv_iterval( (HV*)source, hv_iter );	
				if ( hv_val && sv_deeply_circular( hv_val ) )
					return TRUE;
			}
			break;
		default:
			break;
	};

	sv_depth++;
	return FALSE;
}

MODULE = Clone::Fast		PACKAGE = Clone::Fast

PROTOTYPES: ENABLE

BOOT:
sv_cache   = newHV();
sv_circle  = newHV();

void clone( source )
	SV * source

	PREINIT:
	SV * clone = &PL_sv_undef;

	PPCODE:
#ifdef MINDFUL_REFS
	break_refs      = ( SvTRUE( perl_get_sv( "Clone::Fast::BREAK_REFS",      TRUE ) ) );
#endif
#ifdef MINDFUL_CIR
	ignore_circular = ( SvTRUE( perl_get_sv( "Clone::Fast::IGNORE_CIRCULAR", TRUE ) ) );
#endif
#ifdef ALLOW_HOOKS
	watch_hooks     = ( SvTRUE( perl_get_sv( "Clone::Fast::ALLOW_HOOKS", TRUE ) ) );
#endif
	clone = sv_clone( source );
	hv_clear( sv_cache );
#ifdef MINDFUL_CIR
	hv_clear( sv_circle );
	sv_depth = 0;
#endif
	EXTEND( SP, 1 );
	PUSHs ( sv_2mortal( clone ) );