Ari64 said:
Okay, so the main difference between this approach and having the function return generate the new block is that you'll be able to branch back into the first block, I guess. From a temporal locality of reference point of view I'm not sure if the redundancy hit is a big deal, since hopefully the big tight loops that are run the most won't be full of functions. Of course, in the real world they probably are.. inlining small leaf functions for other reasons might minimize this.
Maybe something I'll have to experiment with later >_>
Ari64 said:
I have to have them in the external block entry table because those internal branches check the cycle count and generate interrupts. When the interrupt returns, it needs to look up those addresses. So I generate entry points for both the branch target and the fall-thru (instruction after the branch).
If you have the PC at the time of the interrupt (which you need regardless) then you can create a new external block entry table, only as necessary. If this table is hashed then it's a good idea to keep the number of entries down.
Ari64 said:
I do end constant propagation at these points, but it's actually pretty rare to find a branch target between a LUI/ADDI pair.
The only analysis that is broken is where I cut down a 64-bit register to 32 bits. Obviously if any register contains an actual 64-bit value, then that entry point can not be used. There are flags in the entry table to identify this situation, and these are checked in jump_eret and get_addr_32 before jumping to any such entry point.
I think correctly handling dynamic register allocation against breaks is the bigger problem, but maybe you are somehow folding the two allocation graphs together.
Ari64 said:
The reason that writing out registers takes so much space for MIPS emulation is that 32-bit values must be sign-extended to 64 bits. For ARM->ARM recompilation (as in a DS emulator) this would not need so many instructions.
For ARM->ARM I would definitely use notaz's approach of static register allocation for most then a small potential for dynamic allocation in the 1 or 2 registers that have to be kept free as temporaries anyway. These would get spilled so often when used as temporaries anyway, I doubt it'd be worth an awful lot to make their spilling conditional around loads and stores.
For GBA (and even DS) emulation you'll see a lot of Thumb code which makes a lot of the registers even less referenced.
Ari64 said:
I don't know in advance where the block ends. To generate the constant pool, I put the values into a queue, and at the end of the block write it out then patch the instructions that reference it. If you don't want to have instructions that load this stuff then I suppose you could put an invalid instruction at the end of the block, then search forward from r14 until you find it, then search from there until you find the data for the location you want. I hope interrupts are infrequent because this will be slow. Or do a binary search tree on r14. But seriously, we are talking about what, 10 bytes of data? It's not that big of a deal.
The invalid instruction approach is what I was thinking. To clarify, when you say interrupts you really just mean exceptions or interrupts caused by/after loads or stores, which in an architecture without MMU would usually be
very infrequent. Interrupts that happen as a result of cycle counting can be dealt with by a separate mechanism.
The actual scheme I have in mind right now is to have interrupts only checked at the start of blocks. Blocks would internal patch forward branches within the same block, but not backwards branches (loops) - yes, this will mean more redundant code generated, but it simplifies a lot of things. Information needed for the interrupt (PC) would be stored in a header before the start of the block. Since interrupts can only happen at the start of blocks it means that nothing special needs to be done for cycle counting or register allocation, since those affairs are already in order, and it's easy to store things before the block starts.
Interrupts that happen due to stores will have to have their information stored either at the end of the block or in a global table. I too considered binary searching a keyed table based on return address; the good thing about this approach is that you don't have to sort the list as you add to it because the translation buffer indexes will be naturally incrementing as you go. You could have a small hash table to speed it up, but really I think these operations will be so infrequent that it doesn't really matter. You would also need it for self modifying code that modifies the same block you're currently in, although that should only happen for stores too.