Leaderboard


Popular Content

Showing content with the highest reputation on 05/31/2021 in Posts

  1. 2 points
    "Might as well jump!" - Van Halen The last update mentioned the mystery of the unconditional jump at the end of a subroutine, and explained it's a return statement. Let's look at it in a bit more detail. 7054 CONSTS [ 7059 CPTOPSP -4 1 7067 ADDxx 7069 CONSTS ] 7074 ADDxx 7076 CPDOWNSP -6 1 7084 MOVSP -4 7090 JMP 7108 *7096 MOVSP -1* //Dead code *7102 MOVSP -3* //Dead code 7108 MOVSP -1 7114 RETN Why is this happening? It's the result of the compiler building the subroutine in stages. Let's separate the op codes into subroutine Parts: Part 2 7054 CONSTS [ 7059 CPTOPSP -4 1 7067 ADDxx 7069 CONSTS ] 7074 ADDxx 7076 CPDOWNSP -6 1 Part 3 (New) 7084 MOVSP -4 7090 JMP 7108 7096 MOVSP -1 Part 3 (Original) 7102 MOVSP -3 Part 4 7108 MOVSP -1 7114 RETN Oho! This subroutine returns a value (Part 2), clears local variables (Part 3), and clears arguments (Part 4). But, why are there two Part 3's? Top to bottom, the stack looks like this: Temporary return values Local variables Arguments Return values The compiler first writes the code for clearing the arguments (Part 4), which is easy because there is a fixed number. Then, it writes code for clearing the local variables (Part 3 - Original). In the example, the subroutine has 3 local variables. Next, the compiler calculates the number of values returned in Part 2 and adds that to the number of local variables. The result is 3 + 1. The compiler does not remove the original Part 3, so it's necessary to jump over it (Part 3 - New). So, why is there a MOVSP after the jump? As far as I can tell, it's a quirk of the compiler. Part 2 returns a value by evaluating a set of expressions. The result is then copied down the stack past the arguments. The result is then cleared in the new Part 3. However, the compiler inserts a MOVSP after the jump that would have cleared the result of the last expression. That leaves us with two MOVSPs that are jumped over: one to clear the local variables, and one to clear the last expression (in this case, the return value). I'm rewriting my code to take advantage of this. Instead of calculating all of the changes made to the stack in order to find the subroutine signature, it could be easier to look for a jump and evaluate the code around it. The question is how to handle subroutines that don't return values, and therefore lack the jump... This newfound knowledge is useful for another reason: I now know how to properly analyze switch statements. More in a future post.