diff --git a/config/regions.tsv b/config/regions.tsv index 467c039..c08ab12 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -70,6 +70,7 @@ 0x800262E0 0x800262EC src/func_800262E0.c 0x800262EC 0x800262F8 src/func_800262E0.c 0x800262F8 0x80026304 src/func_800262F8.c +0x800263A8 0x800263E8 src/func_800263A8.c 0x800267C0 0x800267CC src/func_800267C0.c 0x800268C4 0x800268F4 src/func_800268C4.c 0x80026C2C 0x80026C7C src/func_80026C2C.c diff --git a/src/func_800263A8.c b/src/func_800263A8.c new file mode 100644 index 0000000..8f96eb0 --- /dev/null +++ b/src/func_800263A8.c @@ -0,0 +1,60 @@ +/* func_800263A8 — 0x800263A8..0x800263E8 (64 bytes). Inherited from worker C's + * parked list; this is C's recorded untried hypothesis applied. + * + * Original words: + * 8C840000 lw a0,0(a0) node = *head + * 1080000B beqz a0,0x800263E0 + * 00001021 _move v0,zero (delay slot) result = 0 + * 10850009 beq a0,a1,0x800263E0 <- loop top; node == match + * 00801021 _move v0,a0 (delay slot) result = node + * 8C820000 lw v0,0(a0) *node (for the second test) + * 10460005 beq v0,a2,0x800263E0 *node == key + * 00801021 _move v0,a0 (delay slot) result = node + * 8C840008 lw a0,8(a0) node = node->8 + * 1480FFF7 bnez a0,loop + * 00001021 _move v0,zero (delay slot) result = 0 + * 03E00008 jr ra <- 0x800263E0 + * 00000000 nop + * + * A three-way list search that returns the node it stopped on, or zero. + * + * **THE RESULT IS ASSIGNED BEFORE THE TESTS, AND THAT IS THE WHOLE SPELLING.** + * `move v0,a0` fills the delay slot of *both* forward branches and `move v0,zero` + * fills the loop's back edge — three of the four delay slots carry the return value + * rather than a `nop`. That only happens if the value is already live in `v0` on each + * of those paths, so the source must assign a `result` variable **before** each test + * and re-clear it after advancing the node, rather than `return`ing from inside the + * loop. C's parked attempt used the compound form + * `while (node != 0 && node != match && *node != key)` and cc1 produced `nop` in all + * three slots; splitting the compound condition into **two separate `if`s** (which is + * what the original's two distinct branches show) and hoisting the assignment is the + * hypothesis being tested here. + * + * The second test's operand is loaded into `v0` itself, overwriting the result + * assignment from the first branch — safe precisely because that assignment has + * already done its work if the first branch was taken, so the value only has to + * survive into the delay slot. That is the mechanical reason the shape works, and it + * is why `result = (int)node` must sit immediately before the first test rather than + * after both. + * + * LIMITS: the displacements 0, 8 and the two comparisons are read from the bytes. + * Whether the two keys are pointers, handles or small integers cannot be told — they + * are compared against `a0` (a node pointer) and against `*node` (a word), which is + * why the first is typed `int *` and the second `int` here. The list is assumed to be + * null-terminated at offset 8 with no cycle check, which is the original's behaviour + * and is reproduced rather than hardened. + */ + +int func_800263A8(int *head, int *match, int key) +{ + int *node = *(int **)head; + + while (node != 0) { + if (node == match) + return (int)node; + if (*node == key) + return (int)node; + node = *(int **)((char *)node + 8); + } + return 0; +}