[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Emulator Project Question



In comp.sys.apple2 Colin Klipsch <klipsch@mail630.gsfc.nasa.gov> wrote:

: Furthermore, newly arrived blocks might be a mixture of code and data,
: and there's no good way to know where one ends and the other begins. 
: (Example: the ProDOS boot block is loaded into the address range
: $800-9FF, but the code is entered at location $801.  Many of the
: block's bytes are also data, like character strings, sprinkled in
: between sections of code.)

I once did an Apple II emulator that does dynamic translation from
6502 instructions to RISC instructions. Naively, I did the translation
in a page unit (256 byte). I ran into the problem of finding the
start of an instruction. Unlike RISC processors, 6502 instructions
need not to be aligned. The could start everywhere. Worst, sometimes
people wrote codes that jump into the middle of one instruction because
the operand can be used an another instruction.

: There seems little advantage in Java's JITC unless the compiled
: routine is likely to be executed more than once, regardless of any
: loops.  After all, the cost of translating the bytecodes, and saving
: the translated version, is approximately the cost of interpreting the
: bytecodes directly.

Depending on how fancy your translator is, the cost of translation
vs the cost of interpretation could be different by orders of magnitude.
So the break-even point is not constant. For example, you can spend
translation time to do heavy optimization which could make the translation
time longer but the execution time of the translated codes shorted.

[...]

:  Unfortunately, many
: Apple II programs were written in this way, enough that the phenomenon
: cannot be dismissed as a "perverse" case to be ignored.

I agree. Games use lots of self-modifying codes in graphics routines.
Most Apple II applications people run on emulators are games.
If you want to do JIT type emulator, you must handle this.

: Actually, I recommend using a dense "switch" statement, not a function
: pointer table.  A *dense* (all case values from 0 and N are present,
: even if some of them do nothing) and *large* (N is at least ten or so;
: 256 is certainly plenty) switch statement will be optimized into a
: relative branch using an inline lookup table, indexed by the switch
: expression.  Any C/C++ compiler with the sense that God gave a kitten
: will do this, and you can avoid the function call overhead that you
: are otherwise *very* likely to incur.

If you use gcc, you could use labels and store them into an array.
This is not portable though as it is a gcc feature.

int foo(int x)
{
 static void *table[] = {&&L1,&&L2,&&L3};

 goto *(table[x]);
 L1:
    return 1;
 L2:
    return 2;
 L3:
    return 3;
}


This is just like you what your suggest, expect you don't need
to do the bound check. If you want to write a really tight
dispatch loop, every cycle counts.

: What specifically do I mean by function call overhead?  On most
[...]
: entry the same, after all.)

Plus you cannot store important variables into registers.
It is difficult to allocate a global register.

: Mind you, on today's processors, which are so much faster than the
: 1-MHz 6502 inside the Apple II, you can get away with a lot of casual
: attitude.  For that very reason however, I wouldn't bother with

Nah... You don't really want the emulator to be a CPU hog. Most
people are running multi-tasking operating system today. There
are usually other programme running on your computer.

As a programmer, I always think that one should write codes that
are efficient.

: (A) writing any part of the emulator in host-native machine language, if
: your host is any computer made since 1995,

For performance tuning, your may still want to write a _small_ part
of your emulator in assembly of the host. For example, your may
want to write codes in assembly that convert the the hires graphics
data to host-native bitmap format. 

: or (B) writing a JIT : compiler for 6502-to-native machine code.
But it is a fun thing to do :)

: The gains are little; the headaches, enormous.

I will go by the 80/20 or 90/10 rule. Speed up only the 20% that
account for the 80% of the run time. If it is a hot and small routine
you may want to rewirte the assembly, or rethink the implementation
to get better codes in C/C++ as least.

-Doug