maksverver / BrainfuckVM

Brainfuck VM with JIT code generation
1 stars 0 forks source link

A new (failing) test case for you #1

Closed rdebath closed 8 years ago

rdebath commented 8 years ago

It should do nothing immediately, instead it hangs.

+[>]<-[-
    <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    ++++
    >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
]
maksverver commented 8 years ago

Thanks for the bug report! That's an interesting corner case. How did you run into it?

A simpler way to trigger the same bug, is this: >[-<<+>>]

In optimized mode, that loop gets compiled into "add the value at the head of the tape two places to the left", and we end up adding 0 to an invalid memory location (which triggers a segfault in the interpreter that should terminate the program, but currently it doesn't).

I wonder what the right solution should be. It's possible to test before adding, but that introduces a conditional jump, which makes the generated code longer and possibly slower. An alternative would be to let the signal handler deal with it, which is is a little messy... I'll have to think a little about it.

rdebath commented 8 years ago

What I do is to divide this up into two classes. The common class is a short distance and the rare one a long negative distance. Accesses to the 'right' of the [ are known to be safe, (the tape is 'infinite') so if all accesses are above the [ no condition is needed. I then add a small number of cells to the 'left' of the starting point. Because I know the [ must be to the right of the starting point I can safely access that many cells to the left of the [. If the range is larger than that I put the condition in.

The Mandelbrot program from here witth commonly trigger the problem.

The test seems to hit more interpreters than I though, especially with a long jump, so now I've written a new test for it.

BTW: the +[>]<- thing is my favourite fragment for defeating optimisers.

rdebath commented 8 years ago

I see you'd like some benchmarking ... It looks like the ones I'm using for scoring are unchanged, the bug is fixed. Of the others, 'Collatz' and 'Factor' look substantially slower, however, several of the others are actually quicker!

<table style="font-family:sans-serif;font-size:10pt;border-collapse:collapse;" bgcolor=white border=1px solid #000 >

InterpreterTotalScore BitwidthEndtestCounterPrime8HanoiPrecalcBeerCollatzGoldenLongSelfIntEuler1LifeFactorMandelSkiploopBenchBigTxtLostKngawibZozotezEuler5PrimePIdigitsPIdg-asPIdg-cp 10511812211311111411311211111111010610210110198949183643523194910869 rdb_bf2jit 173s 22.048bitLeave1.05s0.03s0.01s0s0s0.69s0.02s0.2s2.91s0.15s0.01s0.15s1.01s0s0s0.03s0.09s0.02s117sSkipSkip48.5s0.48s0.69s tritium_q 191.6s 21.648bitLeave1.05s0.02s0.04s0s0.01s0.65s0.02s0.01s2.05s0.37s0.01s0.15s0.91s0s0s2.67s0.23s0.07s43.6s122sSkip16.9s0.49s0.32s libbf 15.47s 18.48Zombie80xFF1.23s0.02s0.05s0s0.01s0.72s0.02s0.22s3s200s0.01s0.15s1s0.01s0s0.5s7.99s0.03sSkip0.05s0.03s200s0.56s100s bfli_nb 138.2s 14.388bitLeave1.31s0.31s0.05s0s0.02s0.65s0.02s0.2s2.01s0.13s0.01s0.13s0.92s0s0s0.02s0.05s0.01s94.5sSkipSkip36.8s0.54s0.55s maksverver_BrainfuckVM 166.6s 13.058bitLeave1.21s0.58s0.05s0s0s1s0.02s0.3s2.93s0.14s0.01s0.2s1.19s0s0s0.15s0.07s0.02s112sSkipSkip45.5s0.54s0.69s old_maksverver_bf 172.5s 13.058bit?Leave1.21s0.58s0.05s0s0s0.66s0.02s0.28s2.9s0.15s0.01s0.15s80s120s0s0.16s0.07s0.02s118s200s80s47s0.53s0.71s rmmh_beefit 49.96s 12.788bit0xFF1.15s0.67s0.06s0s0s0.72s0.01s0.21s2.98s0s0.01s0.17s0.91s0s0s0s0s0.02s0sSkipSkip41.9s0.54s0.63s HTML source for the colours. ``` html
InterpreterTotalScore BitwidthEndtestCounterPrime8HanoiPrecalcBeerCollatzGoldenLongSelfIntEuler1LifeFactorMandelSkiploopBenchBigTxtLostKngawibZozotezEuler5PrimePIdigitsPIdg-asPIdg-cp
10511812211311111411311211111111010610210110198949183643523194910869
rdb_bf2jit 173s 22.048bitLeave1.05s0.03s0.01s0s0s0.69s0.02s0.2s2.91s0.15s0.01s0.15s1.01s0s0s0.03s0.09s0.02s117sSkipSkip48.5s0.48s0.69s
tritium_q 191.6s 21.648bitLeave1.05s0.02s0.04s0s0.01s0.65s0.02s0.01s2.05s0.37s0.01s0.15s0.91s0s0s2.67s0.23s0.07s43.6s122sSkip16.9s0.49s0.32s
libbf 15.47s 18.48Zombie80xFF1.23s0.02s0.05s0s0.01s0.72s0.02s0.22s3s200s0.01s0.15s1s0.01s0s0.5s7.99s0.03sSkip0.05s0.03s200s0.56s100s
bfli_nb 138.2s 14.388bitLeave1.31s0.31s0.05s0s0.02s0.65s0.02s0.2s2.01s0.13s0.01s0.13s0.92s0s0s0.02s0.05s0.01s94.5sSkipSkip36.8s0.54s0.55s
maksverver_BrainfuckVM 166.6s 13.058bitLeave1.21s0.58s0.05s0s0s1s0.02s0.3s2.93s0.14s0.01s0.2s1.19s0s0s0.15s0.07s0.02s112sSkipSkip45.5s0.54s0.69s
old_maksverver_bf 172.5s 13.058bit?Leave1.21s0.58s0.05s0s0s0.66s0.02s0.28s2.9s0.15s0.01s0.15s80s120s0s0.16s0.07s0.02s118s200s80s47s0.53s0.71s
rmmh_beefit 49.96s 12.788bit0xFF1.15s0.67s0.06s0s0s0.72s0.01s0.21s2.98s0s0.01s0.17s0.91s0s0s0s0s0.02s0sSkipSkip41.9s0.54s0.63s
```
maksverver commented 8 years ago

Interesting! I figured it could go either way: if the cell is usually nonzero, then the check is just overhead. If the cell is sometimes zero, you could save some time by jumping over the loop, which is especially useful because memory access is relatively expensive. I didn't have the benchmarks to back it up, though.

What do the skip values mean in the benchmark results?

(By the way, if you're in the business of building a benchmark suite: there's at least one more benchmark in my repo that you might want to borrow.)

rdebath commented 8 years ago

The "skip" means it didn't complete in the time allotted and it wasn't really expected to because it's a program that only runs in a reasonable time if the cells are a particular size. "Prime" is calculating all the primes up to 1030, it requires an interpreter that can do "simple loops" on a 16bit+ cell. Euler5 isn't quite as strict but it needs a very fast instruction rate to compensate for the "cell quadrupling" of 8bit cells, OTOH Zozotez just needs 'pretty quick' instructions to compensate for the cell expansion conversions.

In the other direction the "Bench" program has these instruction counts:

Commands executed on 8bit: 268436272
Commands executed on 16bit: 4503599627371312
Commands executed on 32bit: 1267650600228229401496703206192

It can only run on 32bit if the optimiser is exceptional.

maksverver commented 8 years ago

Prime and Euler5 seem to run fast enough, but not produce the correct results. I assume they don't work with 8-bit cells?

Personally I consider the 8-bit cell width restriction an essential part of Brainfuck. Programs that assume arbitrarily large cells aren't true Brainfuck programs, as far as I'm concerned. (It always annoys me a bit when people pass them off as such, especially considering how much more difficult it is to write real Brainfuck code.)

rdebath commented 8 years ago

Use these variants:

https://github.com/rdebath/Brainfuck/blob/master/testing/Euler5-multisize.b https://github.com/rdebath/Brainfuck/blob/master/testing/Prime-doubled.b

The size restriction to the arbitrary 8bits makes no difference IMO, If you need larger numbers doubling or quadrupling it isn't difficult. If those don't work for you the usual fallback is to use decimal because printing out the numbers then becomes trivial.

Hmmm, I think I might change the links on those particular headings.

maksverver commented 8 years ago

Thanks for the links to the multisize versions!

The size restriction to the arbitrary 8bits makes no difference IMO, If you need larger numbers doubling or quadrupling it isn't difficult.

Sure, any higher-level language can be compiled down to Brainfuck, since Brainfuck is a Turing-complete language.

However, the point of writing Brainfuck in the first place is to challenge yourself. Writing code in a higher-level dialect then compiling it down to real Brainfuck is less of a challenge and thus defeats the point of using Brainfuck in the first place. In my opinion, programs written in higher-level languages don't deserve to be called Brainfuck programs.

If I write a program in C and use the compiler to generate the appropriate assembly code, I didn't write assembly code.

Moreover, generated Brainfuck code is typically much larger, less readable (yes, less readable than regular Brainfuck :-P) and usually slower than hand-crafted code. (All of these properties are apparent from the multisize programs you posted.)

If those don't work for you the usual fallback is to use decimal because printing out the numbers then becomes trivial.

Right, which is a much smarter approach anyway, because it leads to code that isn't limited by the size of the cell.

rdebath commented 8 years ago

Yes, I do agree with you for the most part, but I'm saying that it doesn't matter what base you store the numbers in. The algorithms you use to increase the size from a single "digit" are the same if you use base 10, base 100, base 256, base 4294967296 or any other that's appropriate. They are those addition, subtraction, multiplication and division you learnt in primary school. If anything base 10 would easier as it's more familiar. Then also with a non-optimising interpreter base 100 will probably be faster for every case where base 10 is faster than the others (ie: with lots of printing out of large base 10 numbers)

As for readability, yes, for any generated code the readability is usually worse. But that doesn't really apply to simple automated cell doubling. After all if I'd followed my own suggestion the first section of that "multisize" file would be exactly the same as the pure 32bit version, just with the comments that tell you that you don't need to bother to look at the other parts.

Oh, BTW limiting yourself to to using base 10 and so using less than half of each cell is NOT the best way to make unbounded integers.