The control flow would be a bit more natural with a REPEAT loop (tail-tested) instead of WHILE (front-tested), and associating the constant 1 with 'reg1' instead of 'reg2' makes the code a wee bit smoother to read. The loop counter register (reg1 in the original code) is not assigned an explicit value, so it is presumably 0?
It is worth it to keep the separation between main loop and the processing code, instead of inlining the processing. Since the interpreter forces one to jump through major hoops for accessing parameters on the stack, one might as well pass the lone parameter in a register. 'mov reg4 reg1' is still less overhead than the rigmarole for extracting the value from the stack.
const reg1 1 // loop increment
const reg2 101 // upper bound + 1
const reg4 1 // loop variable
loop:
pusha
call process_number_in_reg4
popa
add reg1 reg4
cmp reg2 reg4
ja loop
halt
process_number_in_reg4:
mov reg4 reg1
...
If you want then you can still pull the parameter from the stack exactly as before, since I deviously assigned loop variable duty to reg4.
Similar considerations apply to the rest of the code, which can be tightened considerably. However, Edward has already done a superb job on that, and so it's time to address the more interesting - if implicit - part of the question, which is ToyVM itself.
Overall I must say that both your byte code and your interpreter need seriously serious reworking, starting with the design. The byte code language is lacking many basic features (starting with a simple jae
or jnb
and so on). In some cases this can be worked around without additional instructions by expedients like changing loop boundaries (as with the 101
instead of 100
in the fragment above), but overall the effect is an unnecessary reduction in effectiveness and unnecessary complications for code generators (including human programmers). Assembly programmers and code generator programmers don't mind hoops much if they get something in return, like blistering performance. Conversely, arbitrary limitations without payback are greeted with a decided lack of enthusiasm.
Also, your flags need reworking - take some inspiration from the guys who've been doing this for several decades (Intel, Motorola, Zilog, ...). It just doesn't make sense to have independent 'above' and 'below' flags. If you don't want to do the research for developing a decent flag design, why not simply take the basic x86 scheme (i.e. the arithmetic flags like CF and ZF)?
Also, it would make sense to adopt a simple and strong rule for what is source and destination, e.g. the direction of moves and so on. Use strict Intel rules (first operand is destination) or the AT&T rules, but get rid of the arbitrariness you have now. I found it impossible to work out the meaning of the flags based on operand order and the usual rule that cmp
equates to sub
without assignment of the result... Studying the .c files didn't help either.
The interpreter is several orders of magnitude too slow for the hoops it makes programmers jump through. I've experimented with a simple three-address machine as byte code interpreter for a simple C-like language (with inspiration from FoxPro and C#), and basic processing rate was about 500 million instructions per second. I can only recommend using a three-address code as well because it strikes the best balance between complexity and power on all levels but you should first get some experience working with 0, 1, and 2 address machines (i.e. implementing real algorithms/functions, like the tasks on code challenge sites).
Overall it is a very interesting project, but I would recommend studying existing interpreters and VMs before tackling v2... Byte code systems like FoxPro, Java and/or the Neverwinter Nights script engine can give a lot of inspiration. You can target Java and .NET IL for some hands-on experience with compiling to bytecode/IL, unless you want to stay strictly on the assembly level. Also recommended is doing a stint of assembly programming across many different processor architectures (some code challenge sites allow at least x86 assembly), including some excursions into Brainf*ck and INTERCAL. This will definitely broaden your outlook and you will gather not only lots of ideas but also lots of frustrations about things that are bad in these existing systems and that you would wish to be different in your ideal language (and its next provisional rendering, i.e. ToyVM v2).