diff --git a/src/backend/dispatch.c b/src/backend/dispatch.c index ebe61b4..dbe6164 100644 --- a/src/backend/dispatch.c +++ b/src/backend/dispatch.c @@ -306,12 +306,91 @@ static int emit_lookup_indexed(FILE* out, const FunctionList* funcs) { return 1; } +// Past a few uniform runs, binary search beats a linear chain of range checks. +#define DISPATCH_LINEAR_MAX_RUNS 16u + +static int emit_lookup_search(FILE* out, const FunctionList* funcs, + const char* symbol_suffix) { + FunctionRange* sorted = + (FunctionRange*)malloc(funcs->count * sizeof(*sorted)); + if (!sorted) + return 0; + u32 count = 0; + for (u32 i = 0; i < funcs->count; i++) { + if (funcs->ranges[i].start < funcs->ranges[i].end) + sorted[count++] = funcs->ranges[i]; + } + if (count == 0) { + free(sorted); + return 0; + } + qsort(sorted, count, sizeof(*sorted), range_compare); + // Overlapping ranges keep the linear form to preserve first-match order. + for (u32 i = 1; i < count; i++) { + if (sorted[i - 1u].end > sorted[i].start) { + free(sorted); + return 0; + } + } + + // Coverage guard; module-table generators scrape this form. + fprintf(out, " if (!("); + for (u32 i = 0; i < count;) { + u32 span_end = i + 1u; + while (span_end < count && sorted[span_end - 1u].end == sorted[span_end].start) + span_end++; + fprintf(out, "%s\n (address >= 0x%08Xu && address < 0x%08Xu)", + i ? " ||" : "", sorted[i].start, sorted[span_end - 1u].end); + i = span_end; + } + fprintf(out, "))\n return NULL;\n"); + fprintf(out, " static const u32 range_start[%u] = {\n", count); + for (u32 i = 0; i < count; i++) + fprintf(out, " 0x%08Xu,\n", sorted[i].start); + fprintf(out, " };\n"); + fprintf(out, " static const u32 range_end[%u] = {\n", count); + for (u32 i = 0; i < count; i++) + fprintf(out, " 0x%08Xu,\n", sorted[i].end); + fprintf(out, " };\n"); + fprintf(out, " static const DolRecompFunction range_function[%u] = {\n", + count); + for (u32 i = 0; i < count; i++) + fprintf(out, " func_%08X%s,\n", sorted[i].start, symbol_suffix); + fprintf(out, " };\n"); + fprintf(out, " u32 low = 0u;\n"); + fprintf(out, " u32 high = %uu;\n", count); + fprintf(out, " while (low < high) {\n"); + fprintf(out, " u32 middle = low + ((high - low) >> 1);\n"); + fprintf(out, " if (range_start[middle] <= address)\n"); + fprintf(out, " low = middle + 1u;\n"); + fprintf(out, " else\n"); + fprintf(out, " high = middle;\n"); + fprintf(out, " }\n"); + fprintf(out, " if (low == 0u)\n"); + fprintf(out, " return NULL;\n"); + fprintf(out, " low--;\n"); + fprintf(out, + " if (address < range_end[low] && " + "((address - range_start[low]) & 3u) == 0u)\n"); + fprintf(out, " return range_function[low];\n"); + fprintf(out, " return NULL;\n"); + fprintf(out, "}\n"); + free(sorted); + return 1; +} + void emit_function_lookup(FILE* out, const FunctionList* funcs, const char* symbol_suffix) { fprintf(out, "\nstatic inline DolRecompFunction " "dolrecomp_find_original%s(u32 address) {\n", symbol_suffix); + u32 run_count = 0; + for (u32 first = 0; first < funcs->count; run_count++) + first = uniform_run_end(funcs, first); + if (run_count > DISPATCH_LINEAR_MAX_RUNS && + emit_lookup_search(out, funcs, symbol_suffix)) + return; for (u32 first = 0; first < funcs->count;) { u32 end = uniform_run_end(funcs, first); emit_lookup_run(out, funcs, first, end, symbol_suffix); diff --git a/tests/test_dispatch.c b/tests/test_dispatch.c index b9cc0e4..3c4f012 100644 --- a/tests/test_dispatch.c +++ b/tests/test_dispatch.c @@ -81,7 +81,71 @@ static char* emit_dispatch_to_string(void) { return buf; } +// Many irregular ranges in two gapped spans, as function partitioning emits. +static char* emit_irregular_lookup_to_string(void) { + FunctionList funcs = {0}; + FILE* f = NULL; + char* buf = NULL; + u32 start = BASE; + + for (u32 i = 0; i < 40u; i++) { + u32 width = 0x10u + (i % 5u) * 0x8u; + if (i == 20u) + start = BASE + 0x2000u; + if (!function_list_add(&funcs, start, start + width)) { + function_list_free(&funcs); + return NULL; + } + start += width; + } + + f = tmpfile(); + if (!f) { + function_list_free(&funcs); + return NULL; + } + emit_function_lookup(f, &funcs, ""); + function_list_free(&funcs); + fflush(f); + + long size = ftell(f); + if (size < 0) { + fclose(f); + return NULL; + } + rewind(f); + buf = (char*)malloc((size_t)size + 1u); + if (!buf) { + fclose(f); + return NULL; + } + size_t got = fread(buf, 1, (size_t)size, f); + buf[got] = '\0'; + fclose(f); + return buf; +} + int main(void) { + char* irregular = emit_irregular_lookup_to_string(); + if (!irregular) { + check(0, "irregular: emit lookup"); + } else { + check(strstr(irregular, "static const u32 range_start[40]") != NULL && + strstr(irregular, "static const DolRecompFunction range_function[40]") != NULL && + strstr(irregular, "if (range_start[middle] <= address)") != NULL, + "irregular: many ranges use a sorted search"); + check(strstr(irregular, "return func_") == NULL && + strstr(irregular, "chunk_functions") == NULL, + "irregular: no linear range-test chain remains"); + check(strstr(irregular, "(address >= 0x80003000u && address < 0x80003280u)") != NULL && + strstr(irregular, "(address >= 0x80005000u && address < 0x80005280u)") != NULL, + "irregular: coverage guard lists each contiguous span"); + check(strstr(irregular, "func_80003000,") != NULL && + strstr(irregular, "func_80005000,") != NULL, + "irregular: table covers both spans"); + free(irregular); + } + char* code = emit_dispatch_to_string(); if (!code) { check(0, "emit dispatch helpers");