Skip to content

Releases: lionkor/mcl

v2.0.0

30 Mar 19:48
b5e625b
Compare
Choose a tag to compare
  • Added optimization step (during compilation). This improves runtime performance by up to 2x (for example in examples/primes.mcl). For comparing performance of your own programs, the optimizer can be turned off with --dont-optimize. More details below under "Performance".
  • Added decompiler, can be used via --decompile. More details below under "Decompiler".
  • Added stack check to the VMs push() implementation (in release builds, in debug builds all operations have stack checks).
  • Fixed various little bugs.

Full Changelog: v1.0.1...v2.0.0

Performance

Performance improvements have been made by adding an optimization step to the compilation to bytecode. This optimization step performs very basic as-if replacement/substitution of instructions. For this, some specialized instructions have been added, which the programmer does not need to use (but may). These are not currently in the Reference.md, but they are:

  • dup2: Acts like over over, duplicating the two top elements of the stack. For example, a stack of 1 2 would become 1 2 1 2. over over, if found in the code, is automatically replaced with dup2. This saves some push() and pop(), as it does a simple memmove in practice, and then moves the stack top.
  • inc and dec: Acts like push 1 followed by add, effectively incrementing the stack top by one. The interpreter / VM can increment without moving the stack top by use of inc, so push 1 followed by add is replaced by this. In the same way, push 1 followed by sub is replaced with dec.
  • jz and jnz: These special conditional jump instructions may be the most useful out of the new instructions, as loops frequently check for zero. jz :somewhere is equivalent to push 0 followed by je :somewhere, and jnz <=> jn in the same way. This, again, can save some stack pushing, as the VM is able to simply look at the stack top and determine if it's zero. This is common enough that a lot of architectures have an instruction like this (in some variation).

These replacements can be turned off with the commandline flag --dont-optimize, which also allows you to confirm that your program is 1) not breaking because of optimizations/replacement, and 2) is actually executing faster.

Decompiler

The new and shiny decompiler can decompile mclb files back to compilable mcl. For example, consider the following code:

push 1
:start
push 1
add
dup
dup
print
push 1000
jn :start
halt

This simply counts from 2 to 1000. Compiled & hex-dumped, the binary is:

0000  10 01 00 00  00 00 00 00   03 00 00 00  00 00 00 00   
0010  0b 00 00 00  00 00 00 00   0b 00 00 00  00 00 00 00   
0020  09 00 00 00  00 00 00 00   10 e8 03 00  00 00 00 00   
0030  12 01 00 00  00 00 00 00   0a 00 00 00  00 00 00 00   

Decompiled with --decompile:

# decompiled from 'examples/count.mclb'
# all label names are generated pseudo-randomly
push 1

:kiredi 	# addr=1
inc
dup
dup
print
push 1000
jn :kiredi 	# ->1
halt
# encountered NOT_AN_INSTRUCTION, assuming end of file

Please note that kiredi is a randomly generated string, and one will be generated for each label in the code.
This can, of course, be fed back into the compiler to compile to a new executable.
Also note the optimization step at play here: push 1 add as optimized to inc.

First Release v1.0.1

28 Mar 21:23
75f46df
Compare
Choose a tag to compare

First release, see README.md and Reference.md for more information.