summaryrefslogtreecommitdiff
path: root/tools/build/src/engine/hcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/build/src/engine/hcache.c')
-rw-r--r--tools/build/src/engine/hcache.c519
1 files changed, 519 insertions, 0 deletions
diff --git a/tools/build/src/engine/hcache.c b/tools/build/src/engine/hcache.c
new file mode 100644
index 000000000..3cf15f776
--- /dev/null
+++ b/tools/build/src/engine/hcache.c
@@ -0,0 +1,519 @@
+/*
+ * This file has been donated to Jam.
+ */
+
+/*
+ * Craig W. McPheeters, Alias|Wavefront.
+ *
+ * hcache.c hcache.h - handle cacheing of #includes in source files.
+ *
+ * Create a cache of files scanned for headers. When starting jam, look for the
+ * cache file and load it if present. When finished the binding phase, create a
+ * new header cache. The cache contains files, their timestamps and the header
+ * files found in their scan. During the binding phase of jam, look in the
+ * header cache first for the headers contained in a file. If the cache is
+ * present and valid, use its contents. This results in dramatic speedups with
+ * large projects (e.g. 3min -> 1min startup for one project.)
+ *
+ * External routines:
+ * hcache_init() - read and parse the local .jamdeps file.
+ * hcache_done() - write a new .jamdeps file.
+ * hcache() - return list of headers on target. Use cache or do a scan.
+ *
+ * The dependency file format is an ASCII file with 1 line per target. Each line
+ * has the following fields:
+ * @boundname@ timestamp_sec timestamp_nsec @file@ @file@ @file@ ...
+ */
+
+#ifdef OPT_HEADER_CACHE_EXT
+
+#include "jam.h"
+#include "hcache.h"
+
+#include "hash.h"
+#include "headers.h"
+#include "lists.h"
+#include "modules.h"
+#include "object.h"
+#include "parse.h"
+#include "regexp.h"
+#include "rules.h"
+#include "search.h"
+#include "timestamp.h"
+#include "variable.h"
+
+typedef struct hcachedata HCACHEDATA ;
+
+struct hcachedata
+{
+ OBJECT * boundname;
+ timestamp time;
+ LIST * includes;
+ LIST * hdrscan; /* the HDRSCAN value for this target */
+ int age; /* if too old, we will remove it from cache */
+ HCACHEDATA * next;
+};
+
+
+static struct hash * hcachehash = 0;
+static HCACHEDATA * hcachelist = 0;
+
+static int queries = 0;
+static int hits = 0;
+
+#define CACHE_FILE_VERSION "version 5"
+#define CACHE_RECORD_HEADER "header"
+#define CACHE_RECORD_END "end"
+
+
+/*
+ * Return the name of the header cache file. May return NULL.
+ *
+ * The user sets this by setting the HCACHEFILE variable in a Jamfile. We cache
+ * the result so the user can not change the cache file during header scanning.
+ */
+
+static const char * cache_name( void )
+{
+ static OBJECT * name = 0;
+ if ( !name )
+ {
+ LIST * const hcachevar = var_get( root_module(), constant_HCACHEFILE );
+
+ if ( !list_empty( hcachevar ) )
+ {
+ TARGET * const t = bindtarget( list_front( hcachevar ) );
+
+ pushsettings( root_module(), t->settings );
+ /* Do not expect the cache file to be generated, so pass 0 as the
+ * third argument to search. Expect the location to be specified via
+ * LOCATE, so pass 0 as the fourth arugment.
+ */
+ object_free( t->boundname );
+ t->boundname = search( t->name, &t->time, 0, 0 );
+ popsettings( root_module(), t->settings );
+
+ name = object_copy( t->boundname );
+ }
+ }
+ return name ? object_str( name ) : 0;
+}
+
+
+/*
+ * Return the maximum age a cache entry can have before it is purged from the
+ * cache.
+ */
+
+static int cache_maxage( void )
+{
+ int age = 100;
+ LIST * const var = var_get( root_module(), constant_HCACHEMAXAGE );
+ if ( !list_empty( var ) )
+ {
+ age = atoi( object_str( list_front( var ) ) );
+ if ( age < 0 )
+ age = 0;
+ }
+ return age;
+}
+
+
+/*
+ * Read a netstring. The caveat is that the string can not contain ASCII 0. The
+ * returned value is as returned by object_new().
+ */
+
+OBJECT * read_netstring( FILE * f )
+{
+ unsigned long len;
+ static char * buf = NULL;
+ static unsigned long buf_len = 0;
+
+ if ( fscanf( f, " %9lu", &len ) != 1 )
+ return NULL;
+ if ( fgetc( f ) != (int)'\t' )
+ return NULL;
+
+ if ( len > 1024 * 64 )
+ return NULL; /* sanity check */
+
+ if ( len > buf_len )
+ {
+ unsigned long new_len = buf_len * 2;
+ if ( new_len < len )
+ new_len = len;
+ buf = (char *)BJAM_REALLOC( buf, new_len + 1 );
+ if ( buf )
+ buf_len = new_len;
+ }
+
+ if ( !buf )
+ return NULL;
+
+ if ( fread( buf, 1, len, f ) != len )
+ return NULL;
+ if ( fgetc( f ) != (int)'\n' )
+ return NULL;
+
+ buf[ len ] = 0;
+ return object_new( buf );
+}
+
+
+/*
+ * Write a netstring.
+ */
+
+void write_netstring( FILE * f, char const * s )
+{
+ if ( !s )
+ s = "";
+ fprintf( f, "%lu\t%s\n", (long unsigned)strlen( s ), s );
+}
+
+
+void hcache_init()
+{
+ FILE * f;
+ OBJECT * version = 0;
+ int header_count = 0;
+ const char * hcachename;
+
+ if ( hcachehash )
+ return;
+
+ hcachehash = hashinit( sizeof( HCACHEDATA ), "hcache" );
+
+ if ( !( hcachename = cache_name() ) )
+ return;
+
+ if ( !( f = fopen( hcachename, "rb" ) ) )
+ return;
+
+ version = read_netstring( f );
+
+ if ( !version || strcmp( object_str( version ), CACHE_FILE_VERSION ) )
+ goto bail;
+
+ while ( 1 )
+ {
+ HCACHEDATA cachedata;
+ HCACHEDATA * c;
+ OBJECT * record_type = 0;
+ OBJECT * time_secs_str = 0;
+ OBJECT * time_nsecs_str = 0;
+ OBJECT * age_str = 0;
+ OBJECT * includes_count_str = 0;
+ OBJECT * hdrscan_count_str = 0;
+ int i;
+ int count;
+ LIST * l;
+ int found;
+
+ cachedata.boundname = 0;
+ cachedata.includes = 0;
+ cachedata.hdrscan = 0;
+
+ record_type = read_netstring( f );
+ if ( !record_type )
+ {
+ fprintf( stderr, "invalid %s\n", hcachename );
+ goto cleanup;
+ }
+ if ( !strcmp( object_str( record_type ), CACHE_RECORD_END ) )
+ {
+ object_free( record_type );
+ break;
+ }
+ if ( strcmp( object_str( record_type ), CACHE_RECORD_HEADER ) )
+ {
+ fprintf( stderr, "invalid %s with record separator <%s>\n",
+ hcachename, record_type ? object_str( record_type ) : "<null>" );
+ goto cleanup;
+ }
+
+ cachedata.boundname = read_netstring( f );
+ time_secs_str = read_netstring( f );
+ time_nsecs_str = read_netstring( f );
+ age_str = read_netstring( f );
+ includes_count_str = read_netstring( f );
+
+ if ( !cachedata.boundname || !time_secs_str || !time_nsecs_str ||
+ !age_str || !includes_count_str )
+ {
+ fprintf( stderr, "invalid %s\n", hcachename );
+ goto cleanup;
+ }
+
+ timestamp_init( &cachedata.time, atoi( object_str( time_secs_str ) ),
+ atoi( object_str( time_nsecs_str ) ) );
+ cachedata.age = atoi( object_str( age_str ) ) + 1;
+
+ count = atoi( object_str( includes_count_str ) );
+ for ( l = L0, i = 0; i < count; ++i )
+ {
+ OBJECT * const s = read_netstring( f );
+ if ( !s )
+ {
+ fprintf( stderr, "invalid %s\n", hcachename );
+ list_free( l );
+ goto cleanup;
+ }
+ l = list_push_back( l, s );
+ }
+ cachedata.includes = l;
+
+ hdrscan_count_str = read_netstring( f );
+ if ( !hdrscan_count_str )
+ {
+ fprintf( stderr, "invalid %s\n", hcachename );
+ goto cleanup;
+ }
+
+ count = atoi( object_str( hdrscan_count_str ) );
+ for ( l = L0, i = 0; i < count; ++i )
+ {
+ OBJECT * const s = read_netstring( f );
+ if ( !s )
+ {
+ fprintf( stderr, "invalid %s\n", hcachename );
+ list_free( l );
+ goto cleanup;
+ }
+ l = list_push_back( l, s );
+ }
+ cachedata.hdrscan = l;
+
+ c = (HCACHEDATA *)hash_insert( hcachehash, cachedata.boundname, &found )
+ ;
+ if ( !found )
+ {
+ c->boundname = cachedata.boundname;
+ c->includes = cachedata.includes;
+ c->hdrscan = cachedata.hdrscan;
+ c->age = cachedata.age;
+ timestamp_copy( &c->time, &cachedata.time );
+ }
+ else
+ {
+ fprintf( stderr, "can not insert header cache item, bailing on %s"
+ "\n", hcachename );
+ goto cleanup;
+ }
+
+ c->next = hcachelist;
+ hcachelist = c;
+
+ ++header_count;
+
+ object_free( record_type );
+ object_free( time_secs_str );
+ object_free( time_nsecs_str );
+ object_free( age_str );
+ object_free( includes_count_str );
+ object_free( hdrscan_count_str );
+ continue;
+
+cleanup:
+
+ if ( record_type ) object_free( record_type );
+ if ( time_secs_str ) object_free( time_secs_str );
+ if ( time_nsecs_str ) object_free( time_nsecs_str );
+ if ( age_str ) object_free( age_str );
+ if ( includes_count_str ) object_free( includes_count_str );
+ if ( hdrscan_count_str ) object_free( hdrscan_count_str );
+
+ if ( cachedata.boundname ) object_free( cachedata.boundname );
+ if ( cachedata.includes ) list_free( cachedata.includes );
+ if ( cachedata.hdrscan ) list_free( cachedata.hdrscan );
+
+ goto bail;
+ }
+
+ if ( DEBUG_HEADER )
+ printf( "hcache read from file %s\n", hcachename );
+
+bail:
+ if ( version )
+ object_free( version );
+ fclose( f );
+}
+
+
+void hcache_done()
+{
+ FILE * f;
+ HCACHEDATA * c;
+ int header_count = 0;
+ const char * hcachename;
+ int maxage;
+
+ if ( !hcachehash )
+ return;
+
+ if ( !( hcachename = cache_name() ) )
+ goto cleanup;
+
+ if ( !( f = fopen( hcachename, "wb" ) ) )
+ goto cleanup;
+
+ maxage = cache_maxage();
+
+ /* Print out the version. */
+ write_netstring( f, CACHE_FILE_VERSION );
+
+ c = hcachelist;
+ for ( c = hcachelist; c; c = c->next )
+ {
+ LISTITER iter;
+ LISTITER end;
+ char time_secs_str[ 30 ];
+ char time_nsecs_str[ 30 ];
+ char age_str[ 30 ];
+ char includes_count_str[ 30 ];
+ char hdrscan_count_str[ 30 ];
+
+ if ( maxage == 0 )
+ c->age = 0;
+ else if ( c->age > maxage )
+ continue;
+
+ sprintf( includes_count_str, "%lu", (long unsigned)list_length(
+ c->includes ) );
+ sprintf( hdrscan_count_str, "%lu", (long unsigned)list_length(
+ c->hdrscan ) );
+ sprintf( time_secs_str, "%lu", (long unsigned)c->time.secs );
+ sprintf( time_nsecs_str, "%lu", (long unsigned)c->time.nsecs );
+ sprintf( age_str, "%lu", (long unsigned)c->age );
+
+ write_netstring( f, CACHE_RECORD_HEADER );
+ write_netstring( f, object_str( c->boundname ) );
+ write_netstring( f, time_secs_str );
+ write_netstring( f, time_nsecs_str );
+ write_netstring( f, age_str );
+ write_netstring( f, includes_count_str );
+ for ( iter = list_begin( c->includes ), end = list_end( c->includes );
+ iter != end; iter = list_next( iter ) )
+ write_netstring( f, object_str( list_item( iter ) ) );
+ write_netstring( f, hdrscan_count_str );
+ for ( iter = list_begin( c->hdrscan ), end = list_end( c->hdrscan );
+ iter != end; iter = list_next( iter ) )
+ write_netstring( f, object_str( list_item( iter ) ) );
+ fputs( "\n", f );
+ ++header_count;
+ }
+ write_netstring( f, CACHE_RECORD_END );
+
+ if ( DEBUG_HEADER )
+ printf( "hcache written to %s. %d dependencies, %.0f%% hit rate\n",
+ hcachename, header_count, queries ? 100.0 * hits / queries : 0 );
+
+ fclose ( f );
+
+cleanup:
+ for ( c = hcachelist; c; c = c->next )
+ {
+ list_free( c->includes );
+ list_free( c->hdrscan );
+ object_free( c->boundname );
+ }
+
+ hcachelist = 0;
+ if ( hcachehash )
+ hashdone( hcachehash );
+ hcachehash = 0;
+}
+
+
+LIST * hcache( TARGET * t, int rec, regexp * re[], LIST * hdrscan )
+{
+ HCACHEDATA * c;
+
+ ++queries;
+
+ if ( ( c = (HCACHEDATA *)hash_find( hcachehash, t->boundname ) ) )
+ {
+ if ( !timestamp_cmp( &c->time, &t->time ) )
+ {
+ LIST * const l1 = hdrscan;
+ LIST * const l2 = c->hdrscan;
+ LISTITER iter1 = list_begin( l1 );
+ LISTITER const end1 = list_end( l1 );
+ LISTITER iter2 = list_begin( l2 );
+ LISTITER const end2 = list_end( l2 );
+ while ( iter1 != end1 && iter2 != end2 )
+ {
+ if ( !object_equal( list_item( iter1 ), list_item( iter2 ) ) )
+ iter1 = end1;
+ else
+ {
+ iter1 = list_next( iter1 );
+ iter2 = list_next( iter2 );
+ }
+ }
+ if ( iter1 != end1 || iter2 != end2 )
+ {
+ if ( DEBUG_HEADER )
+ {
+ printf( "HDRSCAN out of date in cache for %s\n",
+ object_str( t->boundname ) );
+ printf(" real : ");
+ list_print( hdrscan );
+ printf( "\n cached: " );
+ list_print( c->hdrscan );
+ printf( "\n" );
+ }
+
+ list_free( c->includes );
+ list_free( c->hdrscan );
+ c->includes = L0;
+ c->hdrscan = L0;
+ }
+ else
+ {
+ if ( DEBUG_HEADER )
+ printf( "using header cache for %s\n", object_str(
+ t->boundname ) );
+ c->age = 0;
+ ++hits;
+ return list_copy( c->includes );
+ }
+ }
+ else
+ {
+ if ( DEBUG_HEADER )
+ printf ("header cache out of date for %s\n", object_str(
+ t->boundname ) );
+ list_free( c->includes );
+ list_free( c->hdrscan );
+ c->includes = L0;
+ c->hdrscan = L0;
+ }
+ }
+ else
+ {
+ int found;
+ c = (HCACHEDATA *)hash_insert( hcachehash, t->boundname, &found );
+ if ( !found )
+ {
+ c->boundname = object_copy( t->boundname );
+ c->next = hcachelist;
+ hcachelist = c;
+ }
+ }
+
+ /* 'c' points at the cache entry. Its out of date. */
+ {
+ LIST * const l = headers1( L0, t->boundname, rec, re );
+
+ timestamp_copy( &c->time, &t->time );
+ c->age = 0;
+ c->includes = list_copy( l );
+ c->hdrscan = list_copy( hdrscan );
+
+ return l;
+ }
+}
+
+#endif /* OPT_HEADER_CACHE_EXT */