/* * Copyright 1993, 1995 Christopher Seiwald. * Copyright 2011 Steven Watanabe * * This file is part of Jam - see jam.c for Copyright information. */ /* * object.c - object manipulation routines * * External functions: * object_new() - create an object from a string * object_new_range() - create an object from a string of given length * object_copy() - return a copy of an object * object_free() - free an object * object_str() - get the string value of an object * object_done() - free string tables * * This implementation builds a hash table of all strings, so that multiple * calls of object_new() on the same string allocate memory for the string once. * Strings are never actually freed. */ #include "jam.h" #include "object.h" #include #include #include #define OBJECT_MAGIC 0xa762e0e3u #ifndef object_copy struct hash_header { #ifndef NDEBUG unsigned int magic; #endif unsigned int hash; struct hash_item * next; }; #endif struct hash_item { struct hash_header header; char data[ 1 ]; }; #define ALLOC_ALIGNMENT (sizeof(struct hash_item) - sizeof(struct hash_header)) typedef struct string_set { unsigned int num; unsigned int size; struct hash_item * * data; } string_set; static string_set strhash; static int strtotal = 0; static int strcount_in = 0; static int strcount_out = 0; /* * Immortal string allocator implementation speeds string allocation and cuts * down on internal fragmentation. */ #define STRING_BLOCK 4096 typedef struct strblock { struct strblock * next; char data[ STRING_BLOCK ]; } strblock; static strblock * strblock_chain = 0; /* Storage remaining in the current strblock */ static char * storage_start = 0; static char * storage_finish = 0; /* * allocate() - Allocate n bytes of immortal string storage. */ static char * allocate( size_t n ) { #ifdef BJAM_NEWSTR_NO_ALLOCATE return (char *)BJAM_MALLOC( n ); #else /* See if we can grab storage from an existing block. */ size_t remaining = storage_finish - storage_start; n = ( ( n + ALLOC_ALIGNMENT - 1 ) / ALLOC_ALIGNMENT ) * ALLOC_ALIGNMENT; if ( remaining >= n ) { char * result = storage_start; storage_start += n; return result; } else /* Must allocate a new block. */ { strblock * new_block; size_t nalloc = n; if ( nalloc < STRING_BLOCK ) nalloc = STRING_BLOCK; /* Allocate a new block and link into the chain. */ new_block = (strblock *)BJAM_MALLOC( offsetof( strblock, data[ 0 ] ) + nalloc * sizeof( new_block->data[ 0 ] ) ); if ( new_block == 0 ) return 0; new_block->next = strblock_chain; strblock_chain = new_block; /* Take future allocations out of the larger remaining space. */ if ( remaining < nalloc - n ) { storage_start = new_block->data + n; storage_finish = new_block->data + nalloc; } return new_block->data; } #endif } static unsigned int hash_keyval( char const * key, int const size ) { unsigned int const magic = 2147059363; unsigned int hash = 0; unsigned int i; for ( i = 0; i < size / sizeof( unsigned int ); ++i ) { unsigned int val; memcpy( &val, key, sizeof( unsigned int ) ); hash = hash * magic + val; key += sizeof( unsigned int ); } { unsigned int val = 0; memcpy( &val, key, size % sizeof( unsigned int ) ); hash = hash * magic + val; } return hash + ( hash >> 17 ); } static void string_set_init( string_set * set ) { set->size = 0; set->num = 4; set->data = (struct hash_item * *)BJAM_MALLOC( set->num * sizeof( struct hash_item * ) ); memset( set->data, 0, set->num * sizeof( struct hash_item * ) ); } static void string_set_done( string_set * set ) { BJAM_FREE( set->data ); } static void string_set_resize( string_set * set ) { unsigned i; string_set new_set; new_set.num = set->num * 2; new_set.size = set->size; new_set.data = (struct hash_item * *)BJAM_MALLOC( sizeof( struct hash_item * ) * new_set.num ); memset( new_set.data, 0, sizeof( struct hash_item * ) * new_set.num ); for ( i = 0; i < set->num; ++i ) { while ( set->data[ i ] ) { struct hash_item * temp = set->data[ i ]; unsigned pos = temp->header.hash % new_set.num; set->data[ i ] = temp->header.next; temp->header.next = new_set.data[ pos ]; new_set.data[ pos ] = temp; } } BJAM_FREE( set->data ); *set = new_set; } static char const * string_set_insert( string_set * set, char const * string, int const size ) { unsigned hash = hash_keyval( string, size ); unsigned pos = hash % set->num; struct hash_item * result; for ( result = set->data[ pos ]; result; result = result->header.next ) if ( !strncmp( result->data, string, size ) && !result->data[ size ] ) return result->data; if ( set->size >= set->num ) { string_set_resize( set ); pos = hash % set->num; } result = (struct hash_item *)allocate( sizeof( struct hash_header ) + size + 1 ); result->header.hash = hash; result->header.next = set->data[ pos ]; #ifndef NDEBUG result->header.magic = OBJECT_MAGIC; #endif memcpy( result->data, string, size ); result->data[ size ] = '\0'; assert( hash_keyval( result->data, size ) == result->header.hash ); set->data[ pos ] = result; strtotal += size + 1; ++set->size; return result->data; } static struct hash_item * object_get_item( OBJECT * obj ) { return (struct hash_item *)( (char *)obj - offsetof( struct hash_item, data ) ); } static void object_validate( OBJECT * obj ) { assert( obj ); assert( object_get_item( obj )->header.magic == OBJECT_MAGIC ); } /* * object_new_range() - create an object from a string of given length */ OBJECT * object_new_range( char const * const string, int const size ) { ++strcount_in; #ifdef BJAM_NO_MEM_CACHE { struct hash_item * const m = (struct hash_item *)BJAM_MALLOC( sizeof( struct hash_header ) + size + 1 ); strtotal += size + 1; memcpy( m->data, string, size ); m->data[ size ] = '\0'; m->header.magic = OBJECT_MAGIC; return (OBJECT *)m->data; } #else if ( !strhash.data ) string_set_init( &strhash ); return (OBJECT *)string_set_insert( &strhash, string, size ); #endif } /* * object_new() - create an object from a string */ OBJECT * object_new( char const * const string ) { return object_new_range( string, strlen( string ) ); } #ifndef object_copy /* * object_copy() - return a copy of an object */ OBJECT * object_copy( OBJECT * obj ) { object_validate( obj ); #ifdef BJAM_NO_MEM_CACHE return object_new( object_str( obj ) ); #else ++strcount_in; return obj; #endif } /* * object_free() - free an object */ void object_free( OBJECT * obj ) { object_validate( obj ); #ifdef BJAM_NO_MEM_CACHE BJAM_FREE( object_get_item( obj ) ); #endif ++strcount_out; } /* * object_str() - return the OBJECT's internal C string */ char const * object_str( OBJECT * obj ) { object_validate( obj ); return (char const *)obj; } /* * object_equal() - compare two objects */ int object_equal( OBJECT * lhs, OBJECT * rhs ) { object_validate( lhs ); object_validate( rhs ); #ifdef BJAM_NO_MEM_CACHE return !strcmp( object_str( lhs ), object_str( rhs ) ); #else assert( ( lhs == rhs ) == !strcmp( object_str( lhs ), object_str( rhs ) ) ); return lhs == rhs; #endif } /* * object_hash() - returns the hash value of an object */ unsigned int object_hash( OBJECT * obj ) { object_validate( obj ); #ifdef BJAM_NO_MEM_CACHE return hash_keyval( object_str( obj ), strlen( object_str( obj ) ) ); #else return object_get_item( obj )->header.hash; #endif } #endif /* * object_done() - free string tables. */ void object_done() { #ifdef BJAM_NEWSTR_NO_ALLOCATE unsigned i; for ( i = 0; i < strhash.num; ++i ) { while ( strhash.data[ i ] ) { struct hash_item * item = strhash.data[ i ]; strhash.data[ i ] = item->header.next; BJAM_FREE( item ); } } #else /* Reclaim string blocks. */ while ( strblock_chain ) { strblock * const n = strblock_chain->next; BJAM_FREE( strblock_chain ); strblock_chain = n; } #endif string_set_done( &strhash ); if ( DEBUG_MEM ) { printf( "%dK in strings\n", strtotal / 1024 ); if ( strcount_in != strcount_out ) printf( "--- %d strings of %d dangling\n", strcount_in - strcount_out, strcount_in ); } }