GP2X Dosbox dynamic recompiler backend


M-HT

Very Active Member
Joined
Nov 30, 2007
Messages
688
Location
Bratislava
Website
github.com
I made some changes to the Dosbox dynamic recompiler backend to speed it up a bit.

The result is on these pages:
http://members.chello.sk/apauer/dosbox6/dosbox6.html - changes in dosbox gp2x version 0.72-6
http://members.chello.sk/apauer/dosbox7/dosbox7.html - changes in dosbox cvs 2009-04-29

The speed increase is quite small.
In the previous version the dynamic core was ~55% faster than the simple core.
In the current version the dynamic core is ~59% faster than the simple core.


I was also interested in how fast (slow) dosbox really is, so I compared these versions of the cputest I'm using.
- dos executable in dosbox (simple and dynamic core)
- statically recompiled dos executable
- native executable

The result are following:
dos executable in dosbox (simple core) - 6m49.460s
dos executable in dosbox (dynamic core) - 4m17.590s
statically recompiled dos executable - 0m9.520s
native executable - 0m1.740s

That means, that in this test the statically recompiled dos executable is 5.47 times slower than native executable and the dos executable in dosbox (dynamic core) is 148.04 times slower than the native executable.

I also tried the cputest with larger data with following results:
statically recompiled dos executable - 2m30.380s
native executable - 0m37.540s

In this test the statically recompiled dos executable was 4.01 times slower than native executable. I didn't test the dos executable in dosbox, because it would take a very long time.

What would be interesting to compare is to run x86 linux executable with qemu user emulation, but unfortunately qemu is not working on arm hosts (AFAIK).
 
Pickle said:
This cputest app, is it something we can use to benchmark on the different handhelds?
There's no problem with the program itself, because the test app is a decoder for .smk (smacker) files, that I wrote for recompiled Albion.
The .smk file that I use for testing dosbox is from a game, so I can't give it away. But you can use your own .smk file for testing.

BTW it's on of the smallest .smk files, that I found in the games I have (less than 1 MB in size).

Also the speed of decoding in dosbox depends on the numbes of cycles you give to dosbox (more cycles = faster decoding).
 
I love this kind of analysis, thanks for posting. I'm wondering some things about your benchmark:

- What compiler did you use to compile it for DOS?
- Is it real mode or protected mode?
- Is it strictly integer?
- Does it use mainly 32bit arithmetic or is it more 8/16bit?

What's most surprising to me is how much slower the statically recompiled version is compared to the native one. I'd like to know some of the techniques you're using (register map, how you handle flags, how you clamp < 32bit arithmetic, how you handle large immediates, how you handle memory, if it's real mode how you handle segmentation, etc). I do wonder if the DOS compiler you're using isn't getting in the way though, especially if it's for real mode.
 
Exophase said:
I love this kind of analysis, thanks for posting. I'm wondering some things about your benchmark:

- What compiler did you use to compile it for DOS?
- Is it real mode or protected mode?
- Is it strictly integer?
- Does it use mainly 32bit arithmetic or is it more 8/16bit?
- Open Watcom 1.6
- protected mode
- yes
- mostly 32bit

Exophase said:
What's most surprising to me is how much slower the statically recompiled version is compared to the native one. I'd like to know some of the techniques you're using (register map, how you handle flags, how you clamp < 32bit arithmetic, how you handle large immediates, how you handle memory, if it's real mode how you handle segmentation, etc). I do wonder if the DOS compiler you're using isn't getting in the way though, especially if it's for real mode.
I'm using static register map - 8 x86 GPRs (eax, ebx, ecx, edx, esi, edi, esp, ebp) + eflags + eip are mapped into 10 arm registers, 1 register for temporary memory address and 5 temporary registers for calculations. Other x86 registers are not supported (segment registers, ...).

Flags are calculated only when necessary and arm native flags are used whenever possible.

8bit and 16bit arithmetic is usually done by shifting the values to the upper byte or halfword of a register, performing the calculation and storing the value back.
For example, the instruction "or bl, al" is translated into:
mov tmp1, ebx, lsl #24
orr tmp1, tmp1, eax, lsl #24
bic ebx, ebx, #0x00ff
orr ebx, ebx, tmp1, lsr #24
where tmp1, ebx, eax are renamed arm registers.

Large immediates are handled with the pseudo-instruction LDR and the literal pool (.ltorg).

My static recompiler only supports 32bit code with flat memory model, so memory is handled directly as-is.
32bit memory access is handled as a 32 load/store when it's clear that the memory address is 32bit aligned, otherwise the memory access is broken into smaller loads/stores.

All instructions are translated individually (1 x86 instruction into 1 or more arm instructions). With the exception of flags calculations, surrounding instructions don't have any effect on the translation of an instruction. Some information could be propagated either forward of backward, but it would be a lot of work with little end-effect (in my opinion).
 
Then, don't you wonder why the native version is performing so much better than yours? I do expect an old DOS compiler to be a bit behind but not that much behind. Maybe the problem is much better suited to ARM.

Nonetheless, DOSBox performance really is pretty bad. If there's a decent JVM that we can use (with good ARM JIT of course) then maybe someone should try JPC instead. That one claims decent performance and would be backed by much better ARM code generation.
 
Exophase said:
Then, don't you wonder why the native version is performing so much better than yours? I do expect an old DOS compiler to be a bit behind but not that much behind. Maybe the problem is much better suited to ARM.
I wouldn't call Open Watcom an old compiler, because it's still developed (as open source) and version 1.6 was released in december 2006. And Watcom compiler is/was known for generating the fastest code (in the dos days and probably also in windows days).

The problem with "slow" recompiled version is, that a lot of x86 instructions can't be translated into 1 arm instruction. It's especially bad with instructions with memory access. For example the instructions like "dec dword [ebx+0x14]". This instruction combines 32bit load, arithmetic instruction, flags calculation (because it's followed by conditional jump) and 32bit store. Also, since it's unknown if the memory adddress is 32bit aligned, the load and store must be broken into small loads and stores. The translated result is this:
add madr, ebx, #(20)
ldrb tmp2, [madr]
ldrb tmp3, [madr, #1]
orr tmp2, tmp2, tmp3, lsl #8
ldrb tmp3, [madr, #2]
orr tmp2, tmp2, tmp3, lsl #16
ldrb tmp3, [madr, #3]
orr tmp1, tmp2, tmp3, lsl #24
subS tmp1, tmp1, #1
strb tmp1, [madr]
mov tmp2, tmp1, lsr #8
strb tmp2, [madr, #1]
mov tmp2, tmp2, lsr #8
strb tmp2, [madr, #2]
mov tmp2, tmp2, lsr #8
strb tmp2, [madr, #3]

I don't consider the recompiled version to be slow, although it could be faster. But that would probably require a totally different approach.

Exophase said:
Nonetheless, DOSBox performance really is pretty bad. If there's a decent JVM that we can use (with good ARM JIT of course) then maybe someone should try JPC instead. That one claims decent performance and would be backed by much better ARM code generation.
The authors claim up to 20% native speed, but the problem is compatibility. I don't know what graphics modes they support. Also nothing is said about emulating sound cards (probably not supported). All the games they are showing are real mode games. So I'm not sure about using JPC for playing games.

As I said, interesting would be to test qemu user emulation (only if they would finish making a working arm version).
 
Thanks for the example. I think that you should try either doing a test for unaligned access or allow the kernel to emulate them, unless they're used constantly (and for RMW you only have to do the test once of course). You can also fold the immediate offset into the memory access. If you do a Pandora version things will be a lot lighter since it supports unaligned loads/stores. I have a feeling that this is chewing cycles more than anything for you.

If all else fails, you can reorder things a bit (using more registers - you say you have two more and you can reuse the memory address register at the end) in order to avoid some of the load-use stalls. You can save 6 cycles this way.
 
Exophase said:
Thanks for the example. I think that you should try either doing a test for unaligned access or allow the kernel to emulate them, unless they're used constantly (and for RMW you only have to do the test once of course). You can also fold the immediate offset into the memory access. If you do a Pandora version things will be a lot lighter since it supports unaligned loads/stores. I have a feeling that this is chewing cycles more than anything for you.
I can try doing a test for unaligned access. I don't know if or how much it helps, but I know it will increase the code size.
I can also try allowing the kernel to emulate them, but I read that doing so is slower than breaking it into smaller loads/stores, so I haven't tried it yet.
Pandora version - the problem is that unaligned access is slower than aligned access. So the question is if it's faster than current approach or not.

Exophase said:
If all else fails, you can reorder things a bit (using more registers - you say you have two more and you can reuse the memory address register at the end) in order to avoid some of the load-use stalls. You can save 6 cycles this way.
I have two more free registers, but I try to avoid using the last one (r9), because it's not a free register on other OS.
Would using only one more register save 6 cycles, or would it require two more registers ?
Reusing memory address register can be done only on reads (not on writes or read-writes), but then I couldn't avoid the load-use stall, so I don't think this is a good idea.
 
M-HT said:
I can try doing a test for unaligned access. I don't know if or how much it helps, but I know it will increase the code size.
I can also try allowing the kernel to emulate them, but I read that doing so is slower than breaking it into smaller loads/stores, so I haven't tried it yet.
Pandora version - the problem is that unaligned access is slower than aligned access. So the question is if it's faster than current approach or not.

I think you have a bit of an odd perspective on this. I can't speak with absolute certainty what the actual real world patterns are like, but I would be surprised if unaligned loads and stores dominate the memory accesses of most x86 apps. It's just not useful to do deliberately in most occasions and I imagine that compilers would try to avoid it. At the very least, the stack is something that will remain aligned, especially for C/C++ programs.

With this in mind:

- If you check for it per-instruction then you should go to a stock function for handling the unaligned case, rather than compiling it into the code. This will result in code that's much smaller than what you have now.
- Yes, of course having the OS emulate it will be slower than your method, but only for cases where the access actually is unaligned (causing an unaligned exception to be raised). If the program makes few or no unaligned accesses then this method will be much, much faster.
- A method that is likely to perform even better is to use the OS to catch the exception, send a signal to your process (if available, ie Linux - if not you might want to try hooking the exception handler if THAT'S available), and patch the instruction that raised the exception so that it now jumps to a routine to perform a misaligned load/store. It's not entirely foolproof, but misaligned accesses will likely occur from repeated locations in code if they're deliberate. In the worst case scenario this would only be somewhat slower than your current method, but it's difficult to imagine such a scenario existing if not done intentionally to defeat your recompiler.

For Cortex-A8 (or other ARM CPUs that may support unaligned loading), allowing the CPU to perform the accesses for you would be much faster than any other possible method. An unaligned access is handled by performing two accesses, rotating them, and combining them - but bear in mind that this still only happens if the address is in fact unaligned. Unlike accesses from NEON, you don't have to specify alignment to get full performance benefits. When you think about it, your byte accesses are already halfway to an unaligned access because transactions appearing on the bus come in the form of full word transfers, at least to RAM, so the result of loads must be rotated and clamped. This is why there's an extra load-use cycle involved. Doing four of these would be much worse than the two the CPU does for unaligned accesses, and also bear in mind that the CPU has a 64-bit bus to dcache so it doesn't even really cost extra to do these two consecutive loads.

M-HT said:
I have two more free registers, but I try to avoid using the last one (r9), because it's not a free register on other OS.
Would using only one more register save 6 cycles, or would it require two more registers ?

Reusing memory address register can be done only on reads (not on writes or read-writes), but then I couldn't avoid the load-use stall, so I don't think this is a good idea.

With one extra the best I can come up with on the spot is this:

ldrb tmp2, [madr]
ldrb tmp3, [madr, #1]
ldrb tmp4, [madr, #2]
orr tmp2, tmp2, tmp3, lsl #8
ldrb tmp3, [madr, #3]
orr tmp2, tmp2, tmp4, lsl #16
orr tmp2, tmp2, tmp3, lsl #24

9 cycles vs 7 if you had another free register. Still an improvement over the 13 yours currently is, and there might be something better. This doesn't apply to writes because they don't have a stall period after storing. While using madr as a temporary wouldn't work for rmw operations I think you will find that pure reads outnumber rmw for any code generated by a compiler worth a damn.

I'm a little skeptical about your comment about r9 - is this referring to the Palm or perhaps the iPhone? zodttd told me this about the iPhone some time ago and I confirmed (as I suspected) that it's fine to modify r9 so long as you restore it before leaving your code, either via return or calling a function you didn't write, which I guess would be relying on it being a constant value. In that way it's like the gp register in MIPS. I doubt the OS relies on it being a certain value during interrupts, since that seems like such a silly design flaw. But then again, if we're talking about Palm, who knows.

By the way, JPC is emulating Linux, so that's proof positive that it can handle protected mode. Beyond that I don't know what its current capabilities are.
 
Exophase said:
I think you have a bit of an odd perspective on this. I can't speak with absolute certainty what the actual real world patterns are like, but I would be surprised if unaligned loads and stores dominate the memory accesses of most x86 apps. It's just not useful to do deliberately in most occasions and I imagine that compilers would try to avoid it. At the very least, the stack is something that will remain aligned, especially for C/C++ programs.
You're probably right, that most accesses are aligned. But accesses to stack are already treated as aligned (with esp base register and sometimes also with ebp base register)

Exophase said:
With this in mind:

- If you check for it per-instruction then you should go to a stock function for handling the unaligned case, rather than compiling it into the code. This will result in code that's much smaller than what you have now.
There are two disadvantages to this approach:
- one less free register used for return address (or storing the return address on stack)
- some overhead - two branches and one move from temporary register to target register (plus one branch to check whether it's aligned address or not)

Exophase said:
- Yes, of course having the OS emulate it will be slower than your method, but only for cases where the access actually is unaligned (causing an unaligned exception to be raised). If the program makes few or no unaligned accesses then this method will be much, much faster.
- A method that is likely to perform even better is to use the OS to catch the exception, send a signal to your process (if available, ie Linux - if not you might want to try hooking the exception handler if THAT'S available), and patch the instruction that raised the exception so that it now jumps to a routine to perform a misaligned load/store. It's not entirely foolproof, but misaligned accesses will likely occur from repeated locations in code if they're deliberate. In the worst case scenario this would only be somewhat slower than your current method, but it's difficult to imagine such a scenario existing if not done intentionally to defeat your recompiler.
As I said, I'll test it and I'll see then.

Exophase said:
For Cortex-A8 (or other ARM CPUs that may support unaligned loading), allowing the CPU to perform the accesses for you would be much faster than any other possible method. An unaligned access is handled by performing two accesses, rotating them, and combining them - but bear in mind that this still only happens if the address is in fact unaligned. Unlike accesses from NEON, you don't have to specify alignment to get full performance benefits. When you think about it, your byte accesses are already halfway to an unaligned access because transactions appearing on the bus come in the form of full word transfers, at least to RAM, so the result of loads must be rotated and clamped. This is why there's an extra load-use cycle involved. Doing four of these would be much worse than the two the CPU does for unaligned accesses, and also bear in mind that the CPU has a 64-bit bus to dcache so it doesn't even really cost extra to do these two consecutive loads.
OK, so in the pandora version the recompiler will always use 32bit access to memory.
BTW this could also help dosbox on Pandora, because it's using similar approach to access unaligned memory.

Exophase said:
With one extra the best I can come up with on the spot is this:

ldrb tmp2, [madr]
ldrb tmp3, [madr, #1]
ldrb tmp4, [madr, #2]
orr tmp2, tmp2, tmp3, lsl #8
ldrb tmp3, [madr, #3]
orr tmp2, tmp2, tmp4, lsl #16
orr tmp2, tmp2, tmp3, lsl #24

9 cycles vs 7 if you had another free register. Still an improvement over the 13 yours currently is, and there might be something better. This doesn't apply to writes because they don't have a stall period after storing. While using madr as a temporary wouldn't work for rmw operations I think you will find that pure reads outnumber rmw for any code generated by a compiler worth a damn.
With two extra registers, do you mean to first read all four bytes and then combine them together ?

Exophase said:
I'm a little skeptical about your comment about r9 - is this referring to the Palm or perhaps the iPhone? zodttd told me this about the iPhone some time ago and I confirmed (as I suspected) that it's fine to modify r9 so long as you restore it before leaving your code, either via return or calling a function you didn't write, which I guess would be relying on it being a constant value. In that way it's like the gp register in MIPS. I doubt the OS relies on it being a certain value during interrupts, since that seems like such a silly design flaw. But then again, if we're talking about Palm, who knows.
I was talking about iPhone and I think that also Windows CE uses r9 register for something. I don't know if Palm uses it.


Exophase said:
By the way, JPC is emulating Linux, so that's proof positive that it can handle protected mode. Beyond that I don't know what its current capabilities are.
Yes, but at the moment the protected mode is only working in interpreted mode - in compiled mode it's still in beta. Also the speed of protected mode is unknown. Even when they finish the compiled mode, I'm not sure they'll reach the same speed as in the real mode.
 
M-HT said:
You're probably right, that most accesses are aligned. But accesses to stack are already treated as aligned (with esp base register and sometimes also with ebp base register)

Or at least accesses to the stack via esp and sometimes ebp. The stack will invariably be accessed through other pointers too.

M-HT said:
- one less free register used for return address (or storing the return address on stack)

One of your temporary registers should be lr. You'll have one less register free in that function but I don't think it'd be called often enough to matter.

M-HT said:
- some overhead - two branches and one move from temporary register to target register (plus one branch to check whether it's aligned address or not)

Three branches? There would be a conditional bl and a bx. For RMW you're working with temporary registers anyway, but if that bothers you it's easy to make 8 different functions for different return values.

Maybe you should profile the amount of unaligned accesses, I'd be curious to see how it turns out. You really seem to be optimizing with only the worst case in mind and at the gross expense of the typical case. I would be surprised if this weren't your single largest performance sink.

M-HT said:
As I said, I'll test it and I'll see then.

What are you going to be testing exactly? Letting the OS do it is the simplest by far, and will probably be as effective as the last option I gave, in practice. If you meant that you'll be doing a test for unaligned access, which is the only test you mentioned considering before, then this unsightly piece of code might be of some use to you:

and temp, madr, #3
sub pc, pc, temp
bl unaligned_access

I haven't tested it but I think it'd work >_> If the address is aligned then it'd be like sub pc, pc, 0 which is effectively setting the pc to pc + 8 and thus skipping the next instruction. If it's set then it'll be like setting the pc to + 5 through + 7, and the lower 2 bits of the pc should be ignored which would make it effectively like pc + 4, executing the next instruction. You wouldn't want to use this on post ARM9 where longer pipelines/branch prediction could make it slow.

Of course I only offer this if you want to avoid smashing flags because the N and Z flags are live at that point, otherwise you should use tst temp, madr, #3; blnz unaligned_access instead.

'course, I still think the OS method will be faster.

M-HT said:
OK, so in the pandora version the recompiler will always use 32bit access to memory.
BTW this could also help dosbox on Pandora, because it's using similar approach to access unaligned memory.

Doesn't DOSBox use readb/readw/readd (and write equivalents) normally? Maybe not for cases where the address is known at compiletime, I haven't checked that deeply, but you're checking for misalignment beforehand there anyway. It's especially unlikely that a constant address would be unaligned.

However, in the off chance that it is, you can do slightly faster unaligned loads than you have been (in DOSBox, not here) if you know what the misalignment is, using two loads, a bic, an and, and an orr. Even in Thumb mode, although you'll have to load the register to and/bic with (should be the same value for both). I only know for sure that this will work on ARM9 and earlier, though.

M-HT said:
With two extra registers, do you mean to first read all four bytes and then combine them together ?

Yes, that's the only way you'll avoid all the stalls.

M-HT said:
I was talking about iPhone and I think that also Windows CE uses r9 register for something. I don't know if Palm uses it.

In that case you should take my advice on iPhone at least. I doubt it's different for the others.

M-HT said:
Yes, but at the moment the protected mode is only working in interpreted mode - in compiled mode it's still in beta. Also the speed of protected mode is unknown. Even when they finish the compiled mode, I'm not sure they'll reach the same speed as in the real mode.

It might have the potential to be faster than real mode since it can optionally not emulate segments and not have to worry about < 32bit arithmetic. Paging might not be a hit over the real mode speed as they seem to use page tables to emulate hardware regions anyway.

I do think that JPC's paging approach has a major flaw for emulating actual OS's that will invalid the page table on cache switches, since they'd have to invalidate their entire 4MB table and it'd slow things down dramatically. The solution is to emulate a TLB in conjunction with their table, which shouldn't be all that much overhead, but they haven't mentioned this approach.
 
Exophase said:
M-HT said:
You're probably right, that most accesses are aligned. But accesses to stack are already treated as aligned (with esp base register and sometimes also with ebp base register)
Or at least accesses to the stack via esp and sometimes ebp. The stack will invariably be accessed through other pointers too.
That's exactly what I meant.

Exophase said:
M-HT said:
As I said, I'll test it and I'll see then.
What are you going to be testing exactly? Letting the OS do it is the simplest by far, and will probably be as effective as the last option I gave, in practice. If you meant that you'll be doing a test for unaligned access, which is the only test you mentioned considering before, then this unsightly piece of code might be of some use to you:

and temp, madr, #3
sub pc, pc, temp
bl unaligned_access

I haven't tested it but I think it'd work >_> If the address is aligned then it'd be like sub pc, pc, 0 which is effectively setting the pc to pc + 8 and thus skipping the next instruction. If it's set then it'll be like setting the pc to + 5 through + 7, and the lower 2 bits of the pc should be ignored which would make it effectively like pc + 4, executing the next instruction. You wouldn't want to use this on post ARM9 where longer pipelines/branch prediction could make it slow.

Of course I only offer this if you want to avoid smashing flags because the N and Z flags are live at that point, otherwise you should use tst temp, madr, #3; blnz unaligned_access instead.

'course, I still think the OS method will be faster.
I meant the OS method, because it's easy to make such recompiled version.

And here are the results with the smaller and the larger .smk file:
0m4.440s (before: 0m9.520s) (native version: 0m1.740s)
1m16.170s (before: 2m30.380s) (native version: 0m37.540s)

Nice speedup.

I'll also test the other methods you mentioned to see how they work.

Exophase said:
Maybe you should profile the amount of unaligned accesses, I'd be curious to see how it turns out. You really seem to be optimizing with only the worst case in mind and at the gross expense of the typical case. I would be surprised if this weren't your single largest performance sink.
I'd say that in this test, most accesses are aligned. Since I wrote it, I was aiming for that. I think that the unaligned accesses are in the library functions.
I haven't really needed the performance, because I was reaching playable speeds in the recompiled games.
Of course, more performance is alway good. :)


Exophase said:
M-HT said:
OK, so in the pandora version the recompiler will always use 32bit access to memory.
BTW this could also help dosbox on Pandora, because it's using similar approach to access unaligned memory.
Doesn't DOSBox use readb/readw/readd (and write equivalents) normally? Maybe not for cases where the address is known at compiletime, I haven't checked that deeply, but you're checking for misalignment beforehand there anyway. It's especially unlikely that a constant address would be unaligned.
DOSBox does use readb/readw/readd. But the host_readd function look like this, when only aligned access is allowed:
Code:
return off[0] | (off[1] << 8) | (off[2] << 16) | (off[3] << 24);
Instead of this, when unaligned access is allowed:
Code:
return *(Bit32u *)off;
So I'm saying that for Pandora, DOSBox can be compiled with unaligned access allowed and it should bring some speed.

Also, in dynamic recompiler backend, I'm reading/writing unaligned memory in similar way, so that should also be changed for Pandora.

Exophase said:
M-HT said:
Yes, but at the moment the protected mode is only working in interpreted mode - in compiled mode it's still in beta. Also the speed of protected mode is unknown. Even when they finish the compiled mode, I'm not sure they'll reach the same speed as in the real mode.
It might have the potential to be faster than real mode since it can optionally not emulate segments and not have to worry about < 32bit arithmetic.
That's not really true.

In protected mode, segment registers are used as selectors into GDT or LDT where the base address is stored.
And even with flat memory model, both Windows and Linux use the fs and/or gs registers to hold address to some structures. In Windows, fs points to the Thread Information Block (or Thread Environment Block). In linux, fs is the base of the per-cpu data area (in kernel) and gs points to thread local data (in usermode).

And 8bit and 16bit arithmetic can be (and is) used also in protected mode.
 
M-HT said:
That's not really true.

In protected mode, segment registers are used as selectors into GDT or LDT where the base address is stored.
And even with flat memory model, both Windows and Linux use the fs and/or gs registers to hold address to some structures. In Windows, fs points to the Thread Information Block (or Thread Environment Block). In linux, fs is the base of the per-cpu data area (in kernel) and gs points to thread local data (in usermode).

And 8bit and 16bit arithmetic can be (and is) used also in protected mode.

I know how the segment registers work in protected mode, which is why I said "optionally." Even though they work this way, almost all protected mode applications will use a flat memory model where the segment selectors don't change, and the segments themselves stay at 0 to 4GB. The benefit of having a dynamic recompiler is that you can track modifications to the effective segments and flush your code so that they're basically compile-time constants, so you can get the benefit of how most 32bit applications work without sacrificing compatibility. If a lot of thrashing of the effective segments is detected then a different strategy can be deployed dynamically that emulates them more on a case-by-case basis.

I was of course referring to the implicit segments, ie cs, ds, ss, and es. gs and fs are only accessed explicitly, so emulating them properly and dynamically doesn't affect recompilation for most of the program.

Yes, I do realize that you can use 8 and 16bit operations in protected mode, but what I meant to say is that 32bit operations will be the most typical in protected mode, while 16bit operations will be the most typical in virtual mode (and the most allowed in real mode). Like usual, the more common case is what will dominate performance.

Congratulations on the speed boost btw ;) I made a rather arbitrary guess that you'd get about 40% native after this modification. Looks like I was not far off.
 
M-HT for dosbox your referring to this? :
Code:
/* Define to 1 to use a unaligned memory access */
/* #undef C_UNALIGNED_MEMORY */

But I would think this would only apply to the c based cores, like normal. (thus the C_*)

You seem to say you can do this different way of accessing memory like you just did with the native app with the dosbox backend? Sure would help to have another nice performance bump. Would this apply to all arm's?

I have to say the misalign'd support in the cortex is nice, alignment is the biggest pains in porting something to ARM.
 
Exophase said:
Congratulations on the speed boost btw ;) I made a rather arbitrary guess that you'd get about 40% native after this modification. Looks like I was not far off.
And thank you for the ideas.

I tried two of those in dosbox.

First I changed the host_readd like this:
Code:
	if ( (Bitu)off & 3 == 0) return	*(Bit32u *)off;
	else return off[0] | (off[1] << 8) | (off[2] << 16) | (off[3] << 24);
and similarly the function host_writed.

The result was about 1 second slower code (4m18.430s vs 4m17.590s).


The second test was to let OS take care of unaligned accesses.

The result was about 4 second faster code (4m13.450s vs 4m17.590s).
Probably not even worth doing.

Pickle said:
M-HT for dosbox your referring to this? :
Code:
/* Define to 1 to use a unaligned memory access */
/* #undef C_UNALIGNED_MEMORY */

But I would think this would only apply to the c based cores, like normal. (thus the C_*)
Yes, that's what I was refering to.
These functions are also used to read/write memory in dynamic core.

Pickle said:
You seem to say you can do this different way of accessing memory like you just did with the native app with the dosbox backend? Sure would help to have another nice performance bump. Would this apply to all arm's?
Based on the test I did, the speed increase will probably to be quite small. But only testing on Pandora will tell for sure.

If I'm not mistaken, unaligned accesses are supported on armv6 (or arm11) and higher.
 
I think you should turn your static recompiler into a dynarec backend for DOSBox :> Not that it'd be a small amount of work...
 
Exophase said:
I think you should turn your static recompiler into a dynarec backend for DOSBox :> Not that it'd be a small amount of work...
The idea is nice, but there are some major problems with it (not that I would even consider doing such a thing :)).
My recompiler only support small subset of what x86 is capable of (dosbox supports much more).
DOSBox uses a different approach, so integrating it together would be difficult if not impossible.
And it's a really lot of work to write something like that.
 
M-HT said:
The idea is nice, but there are some major problems with it (not that I would even consider doing such a thing :)).
My recompiler only support small subset of what x86 is capable of (dosbox supports much more).
DOSBox uses a different approach, so integrating it together would be difficult if not impossible.
And it's a really lot of work to write something like that.

Not that it'd be a small amount of work ;P However, I don't know if it'd be an impossible amount of work. For the instructions you don't support, which I'm guessing are less commonly used, an interface for falling back on DOSBox's interpreter could be used. You wouldn't have to follow most of DOSBox's framework so I don't think integration would be too bad... just need to compile a block when it asks for it. You'd have to implement cycle counting, but that's not necessarily a huge problem - DOSBox is probably designed around single entry single exit blocks like most recompilers, and takes away cycles at the end of the block.

The end result wouldn't be even remotely as fast as the statically recompiled binaries but it'd probably be a lot faster than the current recompiler. Maybe if you released the recompiler source someone else would be interested in trying it, not that I'm encouraging you to do this.
 
Exophase said:
Not that it'd be a small amount of work ;P However, I don't know if it'd be an impossible amount of work. For the instructions you don't support, which I'm guessing are less commonly used, an interface for falling back on DOSBox's interpreter could be used. You wouldn't have to follow most of DOSBox's framework so I don't think integration would be too bad... just need to compile a block when it asks for it. You'd have to implement cycle counting, but that's not necessarily a huge problem - DOSBox is probably designed around single entry single exit blocks like most recompilers, and takes away cycles at the end of the block.

The end result wouldn't be even remotely as fast as the statically recompiled binaries but it'd probably be a lot faster than the current recompiler. Maybe if you released the recompiler source someone else would be interested in trying it, not that I'm encouraging you to do this.
It's not the instructions my recompiler doesn't support, but things like real mode/protected mode, segments/selectors and other features that dosbox supports. That's probably also the reason, why dosbox uses separate functions for reading/writing unknown memory addresses instead of generating a code for reading and writing memory.
One source of speed of my recompiler is, that I'm using arm register to hold the x86 registers (all that I support and use), but dosbox stores them in memory where they can be accessed also by functions and not only by generated code.

As for the source of my recompiler, if anyone is interested, send me a PM and I'll provide you the source code (I've done so a few times).
 
Back
Top