/* $Id: memory.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ * -------------------------------------------------------------------- * see doc/License.txt for License Information * -------------------------------------------------------------------- * * File name: $Id: memory.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ * * Description: * * Modification history: * $Log: memory.c,v $ * Revision 1.2 2000/05/25 22:28:56 jholder * changes routine names to reflect zmachine opcode names per spec 1.0 * * Revision 1.1.1.1 2000/05/10 14:21:34 jholder * * imported * * * -------------------------------------------------------------------- */ /* * memory.c * * Code and data caching routines * */ #include "ztypes.h" /* A cache entry */ typedef struct cache_entry { struct cache_entry *flink; int page_number; zbyte_t data[PAGE_SIZE]; } cache_entry_t; /* Cache chain anchor */ static cache_entry_t *cache = NULL; /* Pseudo translation buffer, one entry each for code and data pages */ static unsigned int current_code_page = 0; static cache_entry_t *current_code_cachep = NULL; static unsigned int current_data_page = 0; static cache_entry_t *current_data_cachep = NULL; static unsigned int calc_data_pages( void ); static cache_entry_t *update_cache( int ); /* * load_cache * * Initialise the cache and any other dynamic memory objects. The memory * required can be split into two areas. Firstly, three buffers are required for * input, output and status line. Secondly, two data areas are required for * writeable data and read only data. The writeable data is the first chunk of * the file and is put into non-paged cache. The read only data is the remainder * of the file which can be paged into the cache as required. Writeable data has * to be memory resident because it cannot be written out to a backing store. * */ void load_cache( void ) { unsigned long file_size; unsigned int i, file_pages, data_pages; cache_entry_t *cachep; /* Allocate output and status line buffers */ line = ( char * ) malloc( screen_cols + 1 ); if ( line == NULL ) { fatal( "load_cache(): Insufficient memory to play game" ); } status_line = ( char * ) malloc( screen_cols + 1 ); if ( status_line == NULL ) { fatal( "load_cache(): Insufficient memory to play game" ); } /* Must have at least one cache page for memory calculation */ cachep = ( cache_entry_t * ) malloc( sizeof ( cache_entry_t ) ); if ( cachep == NULL ) { fatal( "load_cache(): Insufficient memory to play game" ); } cachep->flink = cache; cachep->page_number = 0; cache = cachep; /* Calculate dynamic cache pages required */ if ( h_config & CONFIG_MAX_DATA ) { data_pages = calc_data_pages( ); } else { data_pages = ( h_data_size + PAGE_MASK ) >> PAGE_SHIFT; } data_size = data_pages * PAGE_SIZE; file_size = ( unsigned long ) h_file_size *story_scaler; file_pages = ( unsigned int ) ( ( file_size + PAGE_MASK ) >> PAGE_SHIFT ); /* Allocate static data area and initialise it */ datap = ( zbyte_t * ) malloc( data_size ); if ( datap == NULL ) { fatal( "load_cache(): Insufficient memory to play game" ); } for ( i = 0; i < data_pages; i++ ) { read_page( i, &datap[i * PAGE_SIZE] ); } /* Allocate memory for undo */ undo_datap = ( zbyte_t * ) malloc( data_size ); /* Allocate cache pages and initialise them */ for ( i = data_pages; cachep != NULL && i < file_pages; i++ ) { cachep = ( cache_entry_t * ) malloc( sizeof ( cache_entry_t ) ); if ( cachep != NULL ) { cachep->flink = cache; cachep->page_number = i; read_page( cachep->page_number, cachep->data ); cache = cachep; } } } /* load_cache */ /* * unload_cache * * Deallocate cache and other memory objects. * */ void unload_cache( void ) { cache_entry_t *cachep, *nextp; /* Make sure all output has been flushed */ z_new_line( ); /* Free output buffer, status line and data memory */ free( line ); free( status_line ); free( datap ); free( undo_datap ); /* Free cache memory */ for ( cachep = cache; cachep->flink != NULL; cachep = nextp ) { nextp = cachep->flink; free( cachep ); } } /* unload_cache */ /* * read_code_word * * Read a word from the instruction stream. * */ zword_t read_code_word( void ) { zword_t w; w = ( zword_t ) read_code_byte( ) << 8; w |= ( zword_t ) read_code_byte( ); return ( w ); } /* read_code_word */ /* * read_code_byte * * Read a byte from the instruction stream. * */ zbyte_t read_code_byte( void ) { unsigned int page_number, page_offset; /* Calculate page and offset values */ page_number = ( unsigned int ) ( pc >> PAGE_SHIFT ); page_offset = ( unsigned int ) pc & PAGE_MASK; /* Load page into translation buffer */ if ( page_number != current_code_page ) { current_code_cachep = update_cache( page_number ); current_code_page = page_number; } /* Update the PC */ pc++; /* Return byte from page offset */ if ( !current_code_cachep ) { fatal ( "read_code_byte(): read from non-existant page!\n\t(Your dynamic memory usage _may_ be over 64k in size!)" ); } return ( current_code_cachep->data[page_offset] ); } /* read_code_byte */ /* * read_data_word * * Read a word from the data area. * */ zword_t read_data_word( unsigned long *addr ) { zword_t w; w = ( zword_t ) read_data_byte( addr ) << 8; w |= ( zword_t ) read_data_byte( addr ); return ( w ); } /* read_data_word */ /* * read_data_byte * * Read a byte from the data area. * */ zbyte_t read_data_byte( unsigned long *addr ) { unsigned int page_number, page_offset; zbyte_t value; /* Check if byte is in non-paged cache */ if ( *addr < ( unsigned long ) data_size ) { value = datap[*addr]; } else { /* Calculate page and offset values */ page_number = ( int ) ( *addr >> PAGE_SHIFT ); page_offset = ( int ) *addr & PAGE_MASK; /* Load page into translation buffer */ if ( page_number != current_data_page ) { current_data_cachep = update_cache( page_number ); current_data_page = page_number; } /* Fetch byte from page offset */ if ( current_data_cachep ) { value = current_data_cachep->data[page_offset]; } else { fatal( "read_data_byte(): Fetching data from invalid page!" ); } } /* Update the address */ ( *addr )++; return ( value ); } /* read_data_byte */ /* * calc_data_pages * * Compute the best size for the data area cache. Some games have the data size * header parameter set too low. This causes a write outside of data area on * some games. To alleviate this problem the data area size is set to the * maximum of the restart size, the data size and the end of the dictionary. An * attempt is made to put the dictionary in the data area to stop paging during * a dictionary lookup. Some games have the dictionary end very close to the * 64K limit which may cause problems for machines that allocate memory in * 64K chunks. * */ static unsigned int calc_data_pages( void ) { unsigned long offset, data_end, dictionary_end; int separator_count, word_size, word_count; unsigned int data_pages; /* Calculate end of data area, use restart size if data size is too low */ if ( h_data_size > h_restart_size ) { data_end = h_data_size; } else { data_end = h_restart_size; } /* Calculate end of dictionary table */ offset = h_words_offset; separator_count = read_data_byte( &offset ); offset += separator_count; word_size = read_data_byte( &offset ); word_count = read_data_word( &offset ); dictionary_end = offset + ( word_size * word_count ); /* If data end is too low then use end of dictionary instead */ if ( dictionary_end > data_end ) { data_pages = ( unsigned int ) ( ( dictionary_end + PAGE_MASK ) >> PAGE_SHIFT ); } else { data_pages = ( unsigned int ) ( ( data_end + PAGE_MASK ) >> PAGE_SHIFT ); } return ( data_pages ); } /* calc_data_pages */ /* * update_cache * * Called on a code or data page cache miss to find the page in the cache or * read the page in from disk. The chain is kept as a simple LRU chain. If a * page cannot be found then the page on the end of the chain is reused. If the * page is found, or reused, then it is moved to the front of the chain. * */ static cache_entry_t *update_cache( int page_number ) { cache_entry_t *cachep, *lastp; /* Search the cache chain for the page */ for ( lastp = cache, cachep = cache; cachep->flink != NULL && cachep->page_number && cachep->page_number != page_number; lastp = cachep, cachep = cachep->flink ) ; /* If no page in chain then read it from disk */ if ( cachep->page_number != page_number ) { /* Reusing last cache page, so invalidate cache if page was in use */ if ( cachep->flink == NULL && cachep->page_number ) { if ( current_code_page == ( unsigned int ) cachep->page_number ) { current_code_page = 0; } if ( current_data_page == ( unsigned int ) cachep->page_number ) { current_data_page = 0; } } /* Load the new page number and the page contents from disk */ cachep->page_number = page_number; read_page( page_number, cachep->data ); } /* If page is not at front of cache chain then move it there */ if ( lastp != cache ) { lastp->flink = cachep->flink; cachep->flink = cache; cache = cachep; } return ( cachep ); } /* update_cache */