Learning Rust by writing optimizing Brainfuck interpreter first and then trying to rewrite it as a compile-time interpreter.
Optimizing interpreter works by parsing source code into a vector of IrCommand first, and then by converting it into a vector of Command,
while applying different optimizations to make resulting code more compact and faster.
Also, as it is not possible to read user input during compilation, , in BF programs (reading user input) is ignored, which means that not all BF programs are supported.
- Replacing arbitrary number of
>withmove pointer n cells forward - Replacing arbitrary number of
<withmove pointer n cells backward - Replacing arbitrary number of
+withadd n to current cell - Replacing arbitrary number of
-withsubstract n from current cell - Replacing
[-]withset value of current cell to 0 - Replacing
[>>>]withmove pointer forward n cells at a time, until cell with value=0 found - Replacing
[<<<]withmove pointer backward n cells at a time, until cell with value=0 found
Interpreter was benchmarked before and after implementing optimizations using the following BF-app.
Results on my laptop (Core i7-6700HQ @ 2.60 GHz):
| Time (ms) | |
|---|---|
| Before | 25122.0416 |
| After | 8109.403 |
Currently compile-time is implemented using build script.
All code responsible for parsing and executing BF programs is separated into an interpreter library. That library is then used as a build dependency.
Build.rs loads source code written in BF and calls VM::run method, which optimizes and executes it. The result is then returned back to build.rs and is used to generate result.rs with a single method:
pub fn result() -> &'static str {
"<BF output here>"
}
That file is then included into main.rs, so we can call result() and print it's value:
include!(concat!(env!("OUT_DIR"), "/result.rs"));
fn main() {
println!("{}", result());
}
Resulting source code is available in the lib.rs branch
Using the same Mandelbrot program as before, clean build time is increased up to 24.23 seconds.