diff --git a/core_option_manager.c b/core_option_manager.c index ea35cbcc56..af9a670011 100644 --- a/core_option_manager.c +++ b/core_option_manager.c @@ -644,19 +644,51 @@ static const char *core_option_manager_parse_value_label( return label; } +/* Returns the start of the value list of a legacy + * core options variable ("; ||..."), + * or NULL if it has none */ +static const char *core_option_manager_var_values( + const char *value, size_t *desc_len) +{ + const char *p; + if (!value) + return NULL; + for (p = value; *p; p++) + { + if (p[0] == ';' && p[1] == ' ') + { + *desc_len = (size_t)(p - value); + return p + 2; + } + } + return NULL; +} + +/* Upper bound on the number of values string_split() + * extracts from a legacy value list */ +static size_t core_option_manager_var_num_values(const char *val_start) +{ + size_t n = 1; + for (; *val_start; val_start++) + if (*val_start == '|') + n++; + return n; +} + /* Parses a single legacy core options interface * variable, extracting all present core_option - * information */ + * information. @val_hashes has room for every value + * the variable defines. */ static bool core_option_manager_parse_variable( core_option_manager_t *opt, size_t idx, const struct retro_variable *var, - config_file_t *config_src) + config_file_t *config_src, + uint32_t *val_hashes) { size_t i; union string_list_elem_attr attr; + size_t desc_len = 0; const char *val_start = NULL; - char *value = NULL; - char *desc_end = NULL; struct core_option *option = (struct core_option*)&opt->opts[idx]; struct config_entry_list *entry = NULL; @@ -674,45 +706,31 @@ static bool core_option_manager_parse_variable( option->key_hash = core_option_manager_hash_string(var->key); } - if (var->value && *var->value) - value = strdup(var->value); + if (!(val_start = core_option_manager_var_values(var->value, &desc_len))) + return false; - if (value && *value) + if (desc_len > 0) { - char *p = value; - while (*p) - { - if (p[0] == ';' && p[1] == ' ') - { - desc_end = p; - break; - } - p++; - } + if (!(option->desc = (char*)malloc(desc_len + 1))) + return false; + memcpy(option->desc, var->value, desc_len); + option->desc[desc_len] = '\0'; } - if (!desc_end) - goto error; - - *desc_end = '\0'; - - if (value && *value) - option->desc = strdup(value); - - val_start = desc_end + 2; option->vals = string_split(val_start, "|"); if (!option->vals) - goto error; + return false; /* Legacy core option interface has no concept * of value labels * > Use actual values for display purposes */ attr.i = 0; option->val_labels = string_list_new(); + option->val_hashes = val_hashes; if (!option->val_labels) - goto error; + return false; /* > Loop over values and: * - Set value hashes @@ -720,23 +738,18 @@ static bool core_option_manager_parse_variable( for (i = 0; i < option->vals->size; i++) { const char *value = option->vals->elems[i].data; - uint32_t *value_hash = (uint32_t *)malloc(sizeof(uint32_t)); const char *value_label = core_option_manager_parse_value_label( value, NULL); - if (!value_hash) - goto error; - - /* Set value hash */ - *value_hash = core_option_manager_hash_string(value); - option->vals->elems[i].userdata = (void*)value_hash; + val_hashes[i] = core_option_manager_hash_string(value); /* Redundant safely check... */ if (!value_label || !*value_label) value_label = value; /* Append value label string */ - string_list_append(option->val_labels, value_label, attr); + if (!string_list_append(option->val_labels, value_label, attr)) + return false; } /* Legacy core option interface always uses first @@ -756,11 +769,8 @@ static bool core_option_manager_parse_variable( for (i = 0; i < option->vals->size; i++) { - const char *value = option->vals->elems[i].data; - uint32_t value_hash = *((uint32_t*)option->vals->elems[i].userdata); - - if ( value_hash == entry_value_hash - && string_is_equal(value, entry->value)) + if ( val_hashes[i] == entry_value_hash + && string_is_equal(option->vals->elems[i].data, entry->value)) { option->index = i; break; @@ -774,12 +784,7 @@ static bool core_option_manager_parse_variable( option->info_categorized = NULL; option->category_key = NULL; - free(value); return true; - -error: - free(value); - return false; } /** @@ -804,6 +809,7 @@ core_option_manager_t *core_option_manager_new_vars( { const struct retro_variable *var = NULL; size_t _len = 0; + size_t num_vals = 0; config_file_t *config_src = NULL; core_option_manager_t *opt = NULL; @@ -822,6 +828,7 @@ core_option_manager_t *core_option_manager_new_vars( opt->cats_size = 0; opt->opts = NULL; opt->size = 0; + opt->val_hashes = NULL; opt->option_map = nested_list_init(); opt->updated = false; opt->log = true; @@ -841,24 +848,35 @@ core_option_manager_t *core_option_manager_new_vars( if (src_conf_path && *src_conf_path) config_src = config_file_new_from_path_to_string(src_conf_path); - /* Get number of variables */ + /* Get number of variables and values */ for (var = vars; var->key && var->value; var++) + { + size_t desc_len = 0; + const char *val_start = core_option_manager_var_values( + var->value, &desc_len); + if (val_start) + num_vals += core_option_manager_var_num_values(val_start); _len++; + } - if (_len == 0) + if (_len == 0 || num_vals == 0) goto error; - /* Create options array */ + /* Create options array and value hash pool */ if (!(opt->opts = (struct core_option*)calloc(_len, sizeof(*opt->opts)))) goto error; + if (!(opt->val_hashes = (uint32_t*)malloc(num_vals * sizeof(uint32_t)))) + goto error; opt->size = _len; _len = 0; + num_vals = 0; /* Parse each variable */ for (var = vars; var->key && var->value; _len++, var++) { - if (core_option_manager_parse_variable(opt, _len, var, config_src)) + if (core_option_manager_parse_variable(opt, _len, var, config_src, + opt->val_hashes + num_vals)) { size_t __len = 0; /* If variable is read correctly, add it to @@ -880,6 +898,7 @@ core_option_manager_t *core_option_manager_new_vars( if (!nested_list_add_item(opt->option_map, address, NULL, (const void*)&opt->opts[_len])) goto error; + num_vals += opt->opts[_len].vals->size; } else goto error; @@ -897,13 +916,26 @@ error: return NULL; } +/* Number of values a v2 option definition lists */ +static size_t core_option_manager_def_num_values( + const struct retro_core_option_value *values) +{ + size_t n = 0; + while ( n < RETRO_NUM_CORE_OPTION_VALUES_MAX + && values[n].value && *values[n].value) + n++; + return n; +} + /* Parses a single v2 core options interface * option, extracting all present core_option - * information */ + * information. @val_hashes has room for every value + * the option defines. */ static bool core_option_manager_parse_option( core_option_manager_t *opt, size_t idx, const struct retro_core_option_v2_definition *option_def, - config_file_t *config_src) + config_file_t *config_src, + uint32_t *val_hashes) { size_t i; union string_list_elem_attr attr; @@ -974,21 +1006,14 @@ static bool core_option_manager_parse_option( option->key_hash = core_option_manager_hash_string(key); } - /* Get number of values */ - for (;;) - { - if (!values[num_vals].value || !*values[num_vals].value) - break; - num_vals++; - } - - if (num_vals < 1) + if ((num_vals = core_option_manager_def_num_values(values)) < 1) return false; /* Initialise string lists */ attr.i = 0; option->vals = string_list_new(); option->val_labels = string_list_new(); + option->val_hashes = val_hashes; if (!option->vals || !option->val_labels) return false; @@ -1001,19 +1026,15 @@ static bool core_option_manager_parse_option( for (i = 0; i < num_vals; i++) { const char *value = values[i].value; - uint32_t *value_hash = (uint32_t *)malloc(sizeof(uint32_t)); const char *value_label = values[i].label; - if (!value_hash) - return false; - /* Append value string * > We know that 'value' is always valid */ - string_list_append(option->vals, value, attr); + if (!string_list_append(option->vals, value, attr)) + return false; /* > Set value hash */ - *value_hash = core_option_manager_hash_string(value); - option->vals->elems[option->vals->size - 1].userdata = (void*)value_hash; + val_hashes[i] = core_option_manager_hash_string(value); /* Value label requires additional processing */ value_label = core_option_manager_parse_value_label( @@ -1024,7 +1045,8 @@ static bool core_option_manager_parse_option( value_label = value; /* Append value label string */ - string_list_append(option->val_labels, value_label, attr); + if (!string_list_append(option->val_labels, value_label, attr)) + return false; /* Check whether this value is the default setting */ if (option_def->default_value && *option_def->default_value) @@ -1049,11 +1071,8 @@ static bool core_option_manager_parse_option( for (i = 0; i < option->vals->size; i++) { - const char *value = option->vals->elems[i].data; - uint32_t value_hash = *((uint32_t*)option->vals->elems[i].userdata); - - if ( value_hash == entry_value_hash - && string_is_equal(value, entry->value)) + if ( val_hashes[i] == entry_value_hash + && string_is_equal(option->vals->elems[i].data, entry->value)) { option->index = i; break; @@ -1095,6 +1114,7 @@ core_option_manager_t *core_option_manager_new( struct retro_core_option_v2_definition *option_defs = NULL; size_t cats_size = 0; size_t _len = 0; + size_t num_vals = 0; config_file_t *config_src = NULL; core_option_manager_t *opt = NULL; @@ -1114,6 +1134,7 @@ core_option_manager_t *core_option_manager_new( opt->cats_size = 0; opt->opts = NULL; opt->size = 0; + opt->val_hashes = NULL; opt->option_map = nested_list_init(); opt->updated = false; opt->log = true; @@ -1149,9 +1170,12 @@ core_option_manager_t *core_option_manager_new( for ( option_def = option_defs; option_def->key && option_def->desc && option_def->values[0].value; option_def++) + { + num_vals += core_option_manager_def_num_values(option_def->values); _len++; + } - if (_len == 0) + if (_len == 0 || num_vals == 0) goto error; /* Create categories array */ @@ -1180,12 +1204,15 @@ core_option_manager_t *core_option_manager_new( } } - /* Create options array */ + /* Create options array and value hash pool */ if (!(opt->opts = (struct core_option*)calloc(_len, sizeof(*opt->opts)))) goto error; + if (!(opt->val_hashes = (uint32_t*)malloc(num_vals * sizeof(uint32_t)))) + goto error; opt->size = _len; _len = 0; + num_vals = 0; /* Parse each option * > Note: 'option_def->info == NULL' is valid */ @@ -1193,7 +1220,8 @@ core_option_manager_t *core_option_manager_new( option_def->key && option_def->desc && option_def->values[0].value; _len++, option_def++) { - if (core_option_manager_parse_option(opt, _len, option_def, config_src)) + if (core_option_manager_parse_option(opt, _len, option_def, config_src, + opt->val_hashes + num_vals)) { /* If option is read correctly, add it to * the map */ @@ -1221,6 +1249,7 @@ core_option_manager_t *core_option_manager_new( address, ":", (const void*)&opt->opts[_len])) goto error; + num_vals += opt->opts[_len].vals->size; } else goto error; @@ -1307,6 +1336,7 @@ void core_option_manager_free(core_option_manager_t *opt) free(opt->cats); free(opt->opts); + free(opt->val_hashes); free(opt); } @@ -1519,9 +1549,8 @@ bool core_option_manager_get_val_idx(core_option_manager_t *opt, for (i = 0; i < option->vals->size; i++) { const char *option_val = option->vals->elems[i].data; - uint32_t option_val_hash = *((uint32_t*)option->vals->elems[i].userdata); - if ( (val_hash == option_val_hash) + if ( (val_hash == option->val_hashes[i]) && (option_val && *option_val) && string_is_equal(val, option_val)) { diff --git a/core_option_manager.h b/core_option_manager.h index c306016e99..7f16dcac6e 100644 --- a/core_option_manager.h +++ b/core_option_manager.h @@ -40,6 +40,9 @@ struct core_option char *category_key; struct string_list *vals; struct string_list *val_labels; + /* val_hashes[i]: hash of vals->elems[i].data; points + * into the manager's val_hashes pool */ + uint32_t *val_hashes; /* opt_idx: option index, used for internal * bookkeeping */ size_t opt_idx; @@ -71,6 +74,8 @@ struct core_option_manager struct core_category *cats; struct core_option *opts; nested_list_t *option_map; + /* Value hashes of every option, one allocation */ + uint32_t *val_hashes; size_t cats_size; size_t size; diff --git a/input/input_driver.h b/input/input_driver.h index 018e14fa92..6dc29dac41 100644 --- a/input/input_driver.h +++ b/input/input_driver.h @@ -394,15 +394,6 @@ typedef struct input_remote_state int16_t analog[4][MAX_USERS]; } input_remote_state_t; -typedef struct input_list_element_t -{ - int16_t *state; - unsigned port; - unsigned device; - unsigned index; - unsigned int state_size; -} input_list_element; - /** * Organizes the functions and data structures of each driver that are accessed * by other parts of the input code. The input_driver structs are the "interface" diff --git a/runahead.c b/runahead.c index a274878765..2584de4007 100644 --- a/runahead.c +++ b/runahead.c @@ -88,39 +88,134 @@ static int16_t *runahead_dense_slot(runahead_dense_cache_t *c, } /* ===== END runahead dense input cache ===== */ +/* ===== BEGIN runahead input list ===== + * Tuples the dense cache has no slot for, one flat array; each + * tuple's ids are sized by the highest id written to it. */ static runahead_dense_cache_t *runahead_dense; +/* Last value of every id of one (port, device, index) + * tuple; ids past state_size read 0 */ +typedef struct input_list_element +{ + int16_t *state; + unsigned port; + unsigned device; + unsigned index; + unsigned state_size; +} input_list_element; + +typedef struct runahead_input_list +{ + input_list_element *elems; + unsigned size; + unsigned cap; +} runahead_input_list_t; + +static runahead_input_list_t runahead_input_list; + +/* The list entry of a (port, device, index) tuple, or NULL */ +static input_list_element *runahead_input_list_find( + const runahead_input_list_t *list, + unsigned port, unsigned device, unsigned index) +{ + unsigned i; + for (i = 0; i < list->size; i++) + { + input_list_element *element = &list->elems[i]; + if ( (element->port == port) + && (element->device == device) + && (element->index == index)) + return element; + } + return NULL; +} + +static void runahead_input_list_free(runahead_input_list_t *list) +{ + unsigned i; + for (i = 0; i < list->size; i++) + free(list->elems[i].state); + free(list->elems); + list->elems = NULL; + list->size = 0; + list->cap = 0; +} + +/* Grows the state of @element so that @id indexes it; + * the new ids read 0 */ +static bool runahead_input_element_expand( + input_list_element *element, unsigned id) +{ + int16_t *state; + unsigned new_size = element->state_size ? element->state_size : 32; + while (id >= new_size) + new_size *= 2; + if (!(state = (int16_t*)realloc(element->state, + new_size * sizeof(int16_t)))) + return false; + memset(&state[element->state_size], 0, + (new_size - element->state_size) * sizeof(int16_t)); + element->state = state; + element->state_size = new_size; + return true; +} + +static void runahead_input_state_set_last( + unsigned port, unsigned device, + unsigned index, unsigned id, int16_t value) +{ + runahead_input_list_t *list = &runahead_input_list; + input_list_element *element; + int16_t *slot = runahead_dense_slot(runahead_dense, port, device, index, id); + + if (slot) + { + *slot = value; + return; + } + + if (!(element = runahead_input_list_find(list, port, device, index))) + { + if (list->size == list->cap) + { + unsigned new_cap = list->cap ? list->cap * 2 : 8; + input_list_element *elems = (input_list_element*)realloc( + list->elems, new_cap * sizeof(*elems)); + if (!elems) + return; + list->elems = elems; + list->cap = new_cap; + } + element = &list->elems[list->size++]; + element->state = NULL; + element->port = port; + element->device = device; + element->index = index; + element->state_size = 0; + } + + if ( id >= element->state_size + && !runahead_input_element_expand(element, id)) + return; + element->state[id] = value; +} + static int16_t input_state_get_last(unsigned port, unsigned device, unsigned index, unsigned id) { - runloop_state_t *runloop_st = runloop_state_get_ptr(); + const input_list_element *element; const int16_t *slot = runahead_dense_slot(runahead_dense, port, device, index, id); if (slot) return *slot; - if (runloop_st->input_state_list) - { - int i; - /* find list item */ - for (i = 0; i < runloop_st->input_state_list->size; i++) - { - input_list_element *element = - (input_list_element*)runloop_st->input_state_list->data[i]; - - if ( (element->port == port) - && (element->device == device) - && (element->index == index)) - { - if (id < element->state_size) - return element->state[id]; - break; - } - } - } - + element = runahead_input_list_find( + &runahead_input_list, port, device, index); + if (element && id < element->state_size) + return element->state[id]; return 0; } +/* ===== END runahead input list ===== */ static void free_retro_ctx_load_content_info(struct retro_ctx_load_content_info *dest) @@ -1170,271 +1265,6 @@ void runahead_remember_controller_port_device(void *data, } #endif -static void mylist_resize(my_list *list, - int new_size, bool run_constructor) -{ - int i; - int new_capacity; - int old_size; - void *element = NULL; - if (new_size < 0) - new_size = 0; - new_capacity = new_size; - old_size = list->size; - - if (new_size == old_size) - return; - - if (new_size > list->capacity) - { - void **new_data; - - if (new_capacity < list->capacity * 2) - new_capacity = list->capacity * 2; - - /* Try to realloc. On OOM, leave the list at its current - * capacity and silently no-op the resize - the caller - * (mylist_add_element) tolerates list->data[old_size] - * being NULL: runahead_input_state_set_last guards on - * 'if (element)' before writing to it, and downstream - * code already accepts that runahead state may be - * incomplete. */ - new_data = (void**)realloc( - (void*)list->data, new_capacity * sizeof(void*)); - if (!new_data) - return; - list->data = new_data; - - for (i = list->capacity; i < new_capacity; i++) - list->data[i] = NULL; - - list->capacity = new_capacity; - } - - if (new_size <= list->size) - { - for (i = new_size; i < list->size; i++) - { - element = list->data[i]; - - if (element) - { - list->destructor(element); - list->data[i] = NULL; - } - } - } - else - { - for (i = list->size; i < new_size; i++) - { - list->data[i] = NULL; - if (run_constructor) - list->data[i] = list->constructor(); - } - } - - list->size = new_size; -} - -static void *mylist_add_element(my_list *list) -{ - int old_size; - if (!list) - return NULL; - old_size = list->size; - mylist_resize(list, old_size + 1, true); - /* mylist_resize may have failed to grow on OOM, in which case - * list->size is still old_size and list->data[old_size] is - * out of bounds. Re-check before returning. */ - if (list->size <= old_size) - return NULL; - return list->data[old_size]; -} - -static void mylist_destroy(my_list **list_p) -{ - my_list *list = NULL; - if (!list_p) - return; - - list = *list_p; - - if (list) - { - mylist_resize(list, 0, false); - free(list->data); - free(list); - *list_p = NULL; - } -} - -static void mylist_create(my_list **list_p, int initial_capacity, - constructor_t constructor, destructor_t destructor) -{ - my_list *list = NULL; - - if (!list_p) - return; - - list = *list_p; - if (list) - mylist_destroy(list_p); - - list = (my_list*)malloc(sizeof(my_list)); - if (!list) - { - *list_p = NULL; - return; - } - *list_p = list; - list->size = 0; - list->constructor = constructor; - list->destructor = destructor; - list->data = (void**)calloc(initial_capacity, sizeof(void*)); - /* On calloc OOM, leave list->data NULL and capacity 0; - * mylist_resize's realloc grows from there on first add and - * a subsequent realloc(NULL, n) is well-defined. */ - list->capacity = list->data ? initial_capacity : 0; -} - -static void *input_list_element_constructor(void) -{ - void *ptr = malloc(sizeof(input_list_element)); - input_list_element *element; - - /* NULL-check the outer malloc: the field writes below - * (port/device/index/state_size) would NULL-deref on OOM. - * The caller (runahead_input_state_set_last) already tolerates - * a NULL return: 'if (element) { ... }'. mylist_resize also - * tolerates a NULL constructor result - it just leaves - * list->data[i] = NULL. */ - if (!ptr) - return NULL; - - element = (input_list_element*)ptr; - element->port = 0; - element->device = 0; - element->index = 0; - element->state = (int16_t*)calloc(NAME_MAX_LENGTH, - sizeof(int16_t)); - /* NULL-check the inner calloc too. If it fails the caller's - * 'element->state[id] = value' NULL-derefs. No way to signal - * partial-failure through the constructor callback's return - * type, so free the outer allocation and return NULL to match - * the outer-OOM behaviour. */ - if (!element->state) - { - free(ptr); - return NULL; - } - element->state_size = NAME_MAX_LENGTH; - - return ptr; -} - -static bool input_list_element_realloc(input_list_element *element, - unsigned int new_size) -{ - if (new_size > element->state_size) - { - /* realloc-to-tmp: the pre-patch 'element->state = realloc( - * element->state, ...)' self-assigns NULL on OOM, which - * then made the very next line '&element->state[element-> - * state_size]' perform pointer arithmetic on NULL (UB) and - * the memset trap on a garbage address. */ - int16_t *tmp = (int16_t*)realloc(element->state, - new_size * sizeof(int16_t)); - if (!tmp) - return false; - element->state = tmp; - memset(&element->state[element->state_size], 0, - (new_size - element->state_size) * sizeof(int16_t)); - element->state_size = new_size; - } - return true; -} - -static bool input_list_element_expand(input_list_element *element, - unsigned int new_index) -{ - unsigned int new_size = element->state_size; - if (new_size == 0) - new_size = 32; - while (new_index >= new_size) - new_size *= 2; - return input_list_element_realloc(element, new_size); -} - -static void input_list_element_destructor(void* element_ptr) -{ - input_list_element *element = (input_list_element*)element_ptr; - if (!element) - return; - - free(element->state); - free(element_ptr); -} - -static void runahead_input_state_set_last( - runloop_state_t *runloop_st, - unsigned port, unsigned device, - unsigned index, unsigned id, int16_t value) -{ - size_t i; - input_list_element *element = NULL; - int16_t *slot = runahead_dense_slot(runahead_dense, port, device, index, id); - - if (slot) - { - *slot = value; - return; - } - - if (!runloop_st->input_state_list) - mylist_create(&runloop_st->input_state_list, 16, - input_list_element_constructor, - input_list_element_destructor); - if (!runloop_st->input_state_list) - return; - - /* Find list item */ - for (i = 0; i < (unsigned)runloop_st->input_state_list->size; i++) - { - element = (input_list_element*)runloop_st->input_state_list->data[i]; - if ( (element->port == port) - && (element->device == device) - && (element->index == index) - ) - { - /* Gate the state[id] write on expand success: if expand - * OOM'd, state_size is still too small and writing to - * state[id] would corrupt memory past the buffer. */ - if (id >= element->state_size - && !input_list_element_expand(element, id)) - return; - element->state[id] = value; - return; - } - } - - element = NULL; - if (runloop_st->input_state_list) - element = (input_list_element*) - mylist_add_element(runloop_st->input_state_list); - if (element) - { - element->port = port; - element->device = device; - element->index = index; - /* Same expand-OOM guard as the lookup branch above. */ - if (id >= element->state_size - && !input_list_element_expand(element, id)) - return; - element->state[id] = value; - } -} - static int16_t runahead_input_state_with_logging(unsigned port, unsigned device, unsigned index, unsigned id) { @@ -1451,7 +1281,7 @@ static int16_t runahead_input_state_with_logging(unsigned port, runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY; /*arbitrary limit of up to 65536 elements in state array*/ if (id < 65536) - runahead_input_state_set_last(runloop_st, port, device, index, id, result); + runahead_input_state_set_last(port, device, index, id, result); return result; } return 0; @@ -1513,13 +1343,10 @@ static void runahead_remove_input_state_hook(runloop_state_t *runloop_st) runloop_st->input_state_callback_original; runloop_st->current_core.retro_set_input_state(cbs->state_cb); runloop_st->input_state_callback_original = NULL; - mylist_destroy(&runloop_st->input_state_list); - if (runahead_dense) - { + runahead_input_list_free(&runahead_input_list); free(runahead_dense); runahead_dense = NULL; } - } if (runloop_st->retro_reset_callback_original) { diff --git a/runahead.h b/runahead.h index cdddfad00a..e8c6c6ddbf 100644 --- a/runahead.h +++ b/runahead.h @@ -26,18 +26,6 @@ #define MAX_RUNAHEAD_FRAMES 12 -typedef void *(*constructor_t)(void); -typedef void (*destructor_t )(void*); - -typedef struct my_list_t -{ - void **data; - constructor_t constructor; - destructor_t destructor; - int capacity; - int size; -} my_list; - typedef struct preemptive_frames_data { /* Savestate buffer */ diff --git a/runloop.h b/runloop.h index e13a9c6d64..8009a4e630 100644 --- a/runloop.h +++ b/runloop.h @@ -259,7 +259,6 @@ struct runloop * per access, and a constructor/destructor function-pointer pair * for what amounts to alloc/free of a data buffer. */ retro_ctx_serialize_info_t runahead_savestate_info; - my_list *input_state_list; preempt_t *preempt_data; #endif diff --git a/samples/core_options/Makefile b/samples/core_options/Makefile new file mode 100644 index 0000000000..0024f0e190 --- /dev/null +++ b/samples/core_options/Makefile @@ -0,0 +1,67 @@ +include ../sample_objdir.mk + +TARGET := core_options_test + +CORE_DIR = ../.. +LIBRETRO_COMM_DIR = $(CORE_DIR)/libretro-common +INCDIRS := -I$(LIBRETRO_COMM_DIR)/include -I$(CORE_DIR) -I$(CORE_DIR)/deps + +CC ?= gcc + +# core_option_manager.c as shipped, on the real config_file, +# string_list and nested_list. string_list_append is wrapped so the +# test can fail any one call. +SOURCES_C := \ + core_options_test.c \ + $(CORE_DIR)/core_option_manager.c \ + $(LIBRETRO_COMM_DIR)/lists/string_list.c \ + $(LIBRETRO_COMM_DIR)/lists/nested_list.c \ + $(LIBRETRO_COMM_DIR)/file/config_file.c \ + $(LIBRETRO_COMM_DIR)/file/config_file_io.c \ + $(LIBRETRO_COMM_DIR)/file/file_path.c \ + $(LIBRETRO_COMM_DIR)/file/file_path_io.c \ + $(LIBRETRO_COMM_DIR)/streams/file_stream.c \ + $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c \ + $(LIBRETRO_COMM_DIR)/string/stdstring.c \ + $(LIBRETRO_COMM_DIR)/string/rstrtod.c \ + $(LIBRETRO_COMM_DIR)/compat/compat_strl.c \ + $(LIBRETRO_COMM_DIR)/compat/compat_strldup.c \ + $(LIBRETRO_COMM_DIR)/compat/compat_posix_string.c \ + $(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \ + $(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \ + $(LIBRETRO_COMM_DIR)/time/rtime.c + +CFLAGS += -std=gnu99 -O1 -g1 -Wall -Werror=implicit-function-declaration \ + $(INCDIRS) -DRARCH_INTERNAL -DHAVE_CONFIGFILE -D_GNU_SOURCE +LDFLAGS += -Wl,--wrap=string_list_append + +ifneq ($(SAN),) + SANITIZER := $(SAN) +endif +ifneq ($(SANITIZER),) + CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS) + LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS) +endif + +OBJS := $(call sample_objs,$(SOURCES_C:.c=.o)) + +all: $(TARGET) + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TARGET): $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) -lm + +check: $(TARGET) + ./$(TARGET) + +clean: + rm -rf $(SAMPLE_OBJDIR) + rm -f $(OBJS) $(TARGET) $(TARGET).exe core_options_test.cfg + +.PHONY: all check clean diff --git a/samples/core_options/core_options_test.c b/samples/core_options/core_options_test.c new file mode 100644 index 0000000000..e648d9bacb --- /dev/null +++ b/samples/core_options/core_options_test.c @@ -0,0 +1,322 @@ +/* The core option manager, driven directly. + * + * core_option_manager.c is linked as shipped against the real + * libretro-common config_file, string_list and nested_list. + * string_list_append is wrapped (--wrap) so a test can make the + * Nth append fail and check that the manager refuses the options + * cleanly - no write outside a list, no leak - under ASan. */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "../../core_option_manager.h" +#include "../../msg_hash.h" +#include "../../verbosity.h" + +static unsigned failures = 0; +#define CHECK(cond, msg) do { if (!(cond)) { printf(" FAIL: %s\n", msg); failures++; } } while (0) + +/* --- what core_option_manager.c needs from the frontend ------------- */ + +const char *msg_hash_to_str(enum msg_hash_enums msg) +{ + switch (msg) + { + case MENU_ENUM_LABEL_VALUE_ON: + return "ON"; + case MENU_ENUM_LABEL_VALUE_OFF: + return "OFF"; + case MENU_ENUM_LABEL_ENABLED: + return "enabled"; + case MENU_ENUM_LABEL_DISABLED: + return "disabled"; + default: + break; + } + return "\x01"; +} + +void RARCH_DBG(const char *fmt, ...) { (void)fmt; } + +/* --- the failing append ------------------------------------------- */ + +bool __real_string_list_append(struct string_list *list, const char *elem, + union string_list_elem_attr attr); + +/* 0: never fail; N: the Nth append from now fails */ +static unsigned append_fail_at; +static unsigned append_calls; + +bool __wrap_string_list_append(struct string_list *list, const char *elem, + union string_list_elem_attr attr) +{ + append_calls++; + if (append_fail_at && append_calls == append_fail_at) + return false; + return __real_string_list_append(list, elem, attr); +} + +static void fail_append(unsigned n) +{ + append_calls = 0; + append_fail_at = n; +} + +/* --- fixtures ------------------------------------------------------ */ + +#define CONF_PATH "core_options_test.cfg" + +static void write_conf(const char *text) +{ + FILE *f = fopen(CONF_PATH, "wb"); + if (f) + { + fputs(text, f); + fclose(f); + } +} + +static size_t find_option(core_option_manager_t *opt, const char *key) +{ + size_t i; + for (i = 0; i < opt->size; i++) + if (opt->opts[i].key && !strcmp(opt->opts[i].key, key)) + return i; + return (size_t)-1; +} + +static struct retro_core_option_v2_definition v2_defs[4]; +static struct retro_core_options_v2 v2_opts; + +static void build_v2(void) +{ + memset(v2_defs, 0, sizeof(v2_defs)); + + v2_defs[0].key = "t_res"; + v2_defs[0].desc = "Resolution"; + v2_defs[0].values[0].value = "320x240"; + v2_defs[0].values[1].value = "640x480"; + v2_defs[0].values[1].label = "Double"; + v2_defs[0].values[2].value = "1280x960"; + v2_defs[0].default_value = "640x480"; + + v2_defs[1].key = "t_flag"; + v2_defs[1].desc = "Flag"; + v2_defs[1].values[0].value = "disabled"; + v2_defs[1].values[1].value = "enabled"; + v2_defs[1].default_value = "disabled"; + + /* Every value slot filled: the count stops at the array's end */ + { + static char vals[RETRO_NUM_CORE_OPTION_VALUES_MAX][12]; + int i; + v2_defs[2].key = "t_full"; + v2_defs[2].desc = "Full"; + for (i = 0; i < RETRO_NUM_CORE_OPTION_VALUES_MAX; i++) + { + snprintf(vals[i], sizeof(vals[i]), "%d", i); + v2_defs[2].values[i].value = vals[i]; + } + v2_defs[2].default_value = "5"; + } + + v2_opts.categories = NULL; + v2_opts.definitions = v2_defs; +} + +/* --- v2 interface -------------------------------------------------- */ + +static void t_v2(void) +{ + core_option_manager_t *opt; + size_t i, idx; + + printf(" v2: values, defaults, config value and lookup\n"); + build_v2(); + write_conf("t_flag = \"enabled\"\n"); + fail_append(0); + + opt = core_option_manager_new(CONF_PATH, NULL, &v2_opts, false); + CHECK(opt != NULL, "manager not created"); + if (!opt) + return; + CHECK(opt->size == 3, "option count"); + + i = find_option(opt, "t_res"); + CHECK(i != (size_t)-1, "t_res missing"); + if (i != (size_t)-1) + { + CHECK(opt->opts[i].vals->size == 3, "t_res value count"); + CHECK(opt->opts[i].val_labels->size == 3, "t_res label count"); + CHECK(opt->opts[i].index == 1, "t_res default not applied"); + CHECK(!strcmp(core_option_manager_get_val(opt, i), "640x480"), "t_res value"); + CHECK(!strcmp(core_option_manager_get_val_label(opt, i), "Double"), "t_res label"); + CHECK(core_option_manager_get_val_idx(opt, i, "1280x960", &idx) && idx == 2, + "t_res lookup of the last value"); + CHECK(!core_option_manager_get_val_idx(opt, i, "1280x96", &idx), + "t_res lookup matched a value it does not have"); + } + + i = find_option(opt, "t_flag"); + CHECK(i != (size_t)-1, "t_flag missing"); + if (i != (size_t)-1) + { + CHECK(opt->opts[i].index == 1, "t_flag config value not applied"); + CHECK(!strcmp(core_option_manager_get_val_label(opt, i), "ON"), + "t_flag label not sanitised"); + } + + i = find_option(opt, "t_full"); + CHECK(i != (size_t)-1, "t_full missing"); + if (i != (size_t)-1) + { + CHECK(opt->opts[i].vals->size == RETRO_NUM_CORE_OPTION_VALUES_MAX, + "t_full value count"); + CHECK(opt->opts[i].index == 5, "t_full default"); + CHECK(core_option_manager_get_val_idx(opt, i, "127", &idx) && idx == 127, + "t_full lookup of the last value"); + } + + core_option_manager_free(opt); +} + +/* Each append a v2 parse makes, made to fail in turn: the manager + * must come back NULL with nothing written past a list. The first + * failure is the first value of the first option, which a write + * through 'elems[size - 1]' would have put before the list. */ +static void t_v2_append_failures(void) +{ + unsigned n, total; + core_option_manager_t *opt; + + printf(" v2: every value/label append failing in turn\n"); + build_v2(); + write_conf(""); + + fail_append(0); + opt = core_option_manager_new(CONF_PATH, NULL, &v2_opts, false); + total = append_calls; + core_option_manager_free(opt); + CHECK(total > 0, "no appends counted"); + + for (n = 1; n <= total; n++) + { + fail_append(n); + opt = core_option_manager_new(CONF_PATH, NULL, &v2_opts, false); + if (opt) + { + printf(" FAIL: append %u of %u failed and a manager was still made\n", + n, total); + failures++; + core_option_manager_free(opt); + } + } + fail_append(0); +} + +/* --- legacy interface ---------------------------------------------- */ + +static const struct retro_variable legacy_vars[] = { + { "t_mode", "Mode; fast|accurate|slow" }, + { "t_nodesc", "; on|off" }, + { "t_gaps", "Gaps; a||b|" }, + { NULL, NULL }, +}; + +static void t_legacy(void) +{ + core_option_manager_t *opt; + size_t i, idx; + + printf(" legacy: values, description, empty tokens, config value\n"); + write_conf("t_mode = \"slow\"\nt_gaps = \"b\"\n"); + fail_append(0); + + opt = core_option_manager_new_vars(CONF_PATH, NULL, legacy_vars); + CHECK(opt != NULL, "manager not created"); + if (!opt) + return; + + i = find_option(opt, "t_mode"); + CHECK(i != (size_t)-1, "t_mode missing"); + if (i != (size_t)-1) + { + CHECK(opt->opts[i].desc && !strcmp(opt->opts[i].desc, "Mode"), "t_mode desc"); + CHECK(opt->opts[i].vals->size == 3, "t_mode value count"); + CHECK(opt->opts[i].index == 2, "t_mode config value not applied"); + CHECK(core_option_manager_get_val_idx(opt, i, "fast", &idx) && idx == 0, + "t_mode lookup"); + } + + i = find_option(opt, "t_nodesc"); + CHECK(i != (size_t)-1, "t_nodesc missing"); + if (i != (size_t)-1) + { + CHECK(opt->opts[i].desc == NULL, "t_nodesc was given a description"); + CHECK(!strcmp(core_option_manager_get_val_label(opt, i), "ON"), + "t_nodesc label not sanitised"); + } + + i = find_option(opt, "t_gaps"); + CHECK(i != (size_t)-1, "t_gaps missing"); + if (i != (size_t)-1) + { + CHECK(opt->opts[i].vals->size == 2, "t_gaps kept an empty value"); + CHECK(opt->opts[i].index == 1, "t_gaps config value not applied"); + } + + core_option_manager_free(opt); +} + +static void t_legacy_append_failures(void) +{ + unsigned n, total; + core_option_manager_t *opt; + + printf(" legacy: every label append failing in turn\n"); + write_conf(""); + + fail_append(0); + opt = core_option_manager_new_vars(CONF_PATH, NULL, legacy_vars); + total = append_calls; + core_option_manager_free(opt); + CHECK(total > 0, "no appends counted"); + + for (n = 1; n <= total; n++) + { + fail_append(n); + opt = core_option_manager_new_vars(CONF_PATH, NULL, legacy_vars); + if (opt) + { + printf(" FAIL: append %u of %u failed and a manager was still made\n", + n, total); + failures++; + core_option_manager_free(opt); + } + } + fail_append(0); +} + +int main(void) +{ + printf("core_options\n"); + t_v2(); + t_v2_append_failures(); + t_legacy(); + t_legacy_append_failures(); + remove(CONF_PATH); + + if (failures) + { + printf("%u failure(s)\n", failures); + return 1; + } + printf("all passed\n"); + return 0; +} diff --git a/samples/runahead/Makefile b/samples/runahead/Makefile index b3a0f9d606..e81a41acfb 100644 --- a/samples/runahead/Makefile +++ b/samples/runahead/Makefile @@ -57,6 +57,9 @@ preempt_mask_fragment.c: $(CORE_DIR)/runahead.c dense_cache_fragment.c: $(CORE_DIR)/runahead.c sed -n '/===== BEGIN runahead dense input cache =====/,/===== END runahead dense input cache =====/p' $< > $@ +input_list_fragment.c: $(CORE_DIR)/runahead.c + sed -n '/===== BEGIN runahead input list =====/,/===== END runahead input list =====/p' $< > $@ + # runahead_tmp_dir_private(), against real directories runahead_tmpdir_fragment.c: $(CORE_DIR)/runahead.c sed -n '/===== BEGIN runahead tmp dir check =====/,/===== END runahead tmp dir check =====/p' $< > $@ @@ -66,7 +69,7 @@ runahead_tmpdir_test: runahead_tmpdir_test.c runahead_tmpdir_fragment.c preempt_slab_fragment.c: $(CORE_DIR)/runahead.c sed -n '/===== BEGIN preempt slab =====/,/===== END preempt slab =====/p' $< > $@ -runahead_copy_test.o: runahead_copy_fragment.c preempt_mask_fragment.c dense_cache_fragment.c preempt_slab_fragment.c +runahead_copy_test.o: runahead_copy_fragment.c preempt_mask_fragment.c dense_cache_fragment.c preempt_slab_fragment.c input_list_fragment.c %.o: %.c $(CC) $(INCDIRS) $(CFLAGS) $(flags) -c -o$@ $< @@ -84,7 +87,7 @@ check: $(TARGET) runahead_tmpdir_test clean: rm -rf $(SAMPLE_OBJDIR) - rm -f $(OBJS) runahead_copy_fragment.c preempt_mask_fragment.c dense_cache_fragment.c preempt_slab_fragment.c runahead_tmpdir_fragment.c + rm -f $(OBJS) runahead_copy_fragment.c preempt_mask_fragment.c dense_cache_fragment.c preempt_slab_fragment.c input_list_fragment.c runahead_tmpdir_fragment.c rm -f $(TARGET) runahead_tmpdir_test .PHONY: clean all check diff --git a/samples/runahead/runahead_copy_test.c b/samples/runahead/runahead_copy_test.c index 5165596aed..c99b1d2e76 100644 --- a/samples/runahead/runahead_copy_test.c +++ b/samples/runahead/runahead_copy_test.c @@ -172,6 +172,21 @@ static bool runahead_tmp_dir_private(const char *dir) #include "dense_cache_fragment.c" #include "preempt_slab_fragment.c" +/* The input list, verbatim from runahead.c, with its reallocs routed + * through one that can be told to fail the Nth call. */ +static unsigned list_realloc_fail_at; +static unsigned list_realloc_calls; +static void *list_realloc(void *ptr, size_t len) +{ + list_realloc_calls++; + if (list_realloc_fail_at && list_realloc_calls == list_realloc_fail_at) + return NULL; + return realloc(ptr, len); +} +#define realloc list_realloc +#include "input_list_fragment.c" +#undef realloc + /* The dense cache: every common tuple has a slot, distinct per tuple; * everything else is refused to the list; a set then get reads back. */ static void t_dense_cache(void) @@ -482,6 +497,84 @@ static void t_tmp_dir_not_private(void) task_queue_deinit(); } +/* Values the input logging recorded read back per tuple and id: past + * the list's first growths, across a state that grows under ids it + * already holds, and with unknown tuples and ids reading 0. */ +static void t_input_list(void) +{ + unsigned port; + runahead_input_list_t *list = &runahead_input_list; + + printf(" input_list: tuples outside the dense cache read back\n"); + list_realloc_fail_at = 0; + runahead_dense = (runahead_dense_cache_t*)calloc(1, sizeof(*runahead_dense)); + + for (port = 0; port < 20; port++) + runahead_input_state_set_last(port, + RETRO_DEVICE_KEYBOARD, 0, 300, (int16_t)(1000 + port)); + runahead_input_state_set_last(0, RETRO_DEVICE_POINTER, 0, 3, 7); + runahead_input_state_set_last(0, RETRO_DEVICE_POINTER, 0, 100, 9); + runahead_input_state_set_last(1, RETRO_DEVICE_JOYPAD, 0, 4, 5); + + CHECK(list->size == 21, "list holds %u tuples, expected 21", list->size); + for (port = 0; port < 20; port++) + CHECK(input_state_get_last(port, RETRO_DEVICE_KEYBOARD, 0, 300) == (int16_t)(1000 + port), + "keyboard port %u read back wrong", port); + CHECK(input_state_get_last(0, RETRO_DEVICE_POINTER, 0, 3) == 7, + "pointer id 3 lost when the state grew for id 100"); + CHECK(input_state_get_last(0, RETRO_DEVICE_POINTER, 0, 100) == 9, "pointer id 100"); + CHECK(input_state_get_last(0, RETRO_DEVICE_POINTER, 0, 50) == 0, "unset id read nonzero"); + CHECK(input_state_get_last(0, RETRO_DEVICE_POINTER, 0, 60000) == 0, "id past the state read nonzero"); + CHECK(input_state_get_last(5, RETRO_DEVICE_POINTER, 0, 3) == 0, "unknown tuple read nonzero"); + CHECK(input_state_get_last(1, RETRO_DEVICE_JOYPAD, 0, 4) == 5, "dense tuple"); + CHECK(!runahead_input_list_find(list, 1, RETRO_DEVICE_JOYPAD, 0), + "a dense tuple went to the list"); + + runahead_input_list_free(list); + CHECK(!list->elems && !list->size && !list->cap, "free left the list set"); + free(runahead_dense); + runahead_dense = NULL; +} + +/* Every realloc the list makes, failing in turn: the write is dropped, + * nothing reads freed or unallocated memory, and later writes land. */ +static void t_input_list_oom(void) +{ + unsigned n, port, total; + runahead_input_list_t *list = &runahead_input_list; + + printf(" input_list: every realloc failing in turn\n"); + + list_realloc_fail_at = 0; + list_realloc_calls = 0; + for (port = 0; port < 12; port++) + runahead_input_state_set_last(port, + RETRO_DEVICE_POINTER, 0, port * 20, 1); + total = list_realloc_calls; + runahead_input_list_free(list); + + for (n = 1; n <= total; n++) + { + list_realloc_fail_at = n; + list_realloc_calls = 0; + for (port = 0; port < 12; port++) + { + runahead_input_state_set_last(port, + RETRO_DEVICE_POINTER, 0, port * 20, 1); + (void)input_state_get_last(port, RETRO_DEVICE_POINTER, 0, port * 20); + } + list_realloc_fail_at = 0; + for (port = 0; port < 12; port++) + { + runahead_input_state_set_last(port, + RETRO_DEVICE_POINTER, 0, port * 20, 2); + CHECK(input_state_get_last(port, RETRO_DEVICE_POINTER, 0, port * 20) == 2, + "realloc %u failed: port %u not recorded afterwards", n, port); + } + runahead_input_list_free(list); + } +} + int main(void) { /* A stray 'return false' from a status-returning function must @@ -491,6 +584,8 @@ int main(void) printf("runahead copy:\n"); t_analog_mask_bit(); t_dense_cache(); + t_input_list(); + t_input_list_oom(); t_preempt_slab(); t_lcg_names(); t_lcg_all_fail();