Lonami / pyndustric

A Python compiler for Mindustry's logic processors assembly language
MIT License
49 stars 9 forks source link

If compiles inefficiently #9

Closed TelosTelos closed 3 years ago

TelosTelos commented 3 years ago

Currently the following

if A:
     B
C

Compiles to the following pseudo-code:

if A != 0: jump to B
jump to C
B
C

It would be more efficient for this to instead compile to:

if A==0: jump to C
B
C

This is more efficient in terms of instruction count, and in terms of operations executed in the case where A is False.

It also looks like you don't make use of the mlog's ability to perform comparisons within the jump command, and instead require that the comparison be performed in a separate op beforehand?

TelosTelos commented 3 years ago

Ah, I see you made a note to yourself: "# TODO negate initial jmp condition with no else" which probably means the same thing as all but the last paragraph above...

Lonami commented 3 years ago

Yes, the TODO is exactly to address your original concern.

It also looks like you don't make use of the mlog's ability to perform comparisons within the jump command, and instead require that the comparison be performed in a separate op beforehand?

No, that shouldn't be the case, all supported operations are compiled to mlog's equivalent (note line 102):

https://github.com/Lonami/pyndustric/blob/a9ced596da27948880ef184170bce236ddf0c3c9/pyndustric/compiler.py#L98-L108


In any case for all these optimization-related concerns, we probably want separate machinery to run a "optimizer pass" after the main compilation is done. For example:

jump 2
jump 3
set x 1

…can turn "jump 2" into "jump 3" to avoid an unnecessary step, and then both can be removed because it's jumping right to the next instruction. This occurs a fair bit with if blocks.

TelosTelos commented 3 years ago

Ah good. I just wasn't looking at the right place. Sorry.

Relatedly though, one of the BIN_CMP constants is this:

    ast.And: 'land',

What is this? If it is a form of And, why isn't there a corresponding form of Or?

Lonami commented 3 years ago

As I understand it, it is a "logic AND" (as opposed to the default bitwise AND). Unless I missed it while listing all the opcodes from in-game, I didn't find a "logical OR" in the UI editor.

TelosTelos commented 3 years ago

So currently we have no way of handling a Python command like if A or B: C? Both because of the complexity of the condition (issue #2), and because we don't have logical OR? It seems like this is something we should support.

It seems like mlog treats most "False" things as 0, and many "True" things as non-zero. So long as the "True" things are all positive, OR is equivalent to addition. However, if there are negative True things, addition can take two "True" values with opposite sign and make them "False": e.g., (-1 + 1) would be 0, hence False, whereas in Python, bool(-1 or 1) is True. To avoid this, we could compile A or B as abs(A)+abs(B) though twould be a shame to require 3 instructions for OR. (We might be able to detect various cases where the abs would be unneeded, or could encourage coders to use + in cases where it would be more efficient. We'll also need to take care with how 2D Vectors behave in boolean ops.)

Another option would be to use DeMorgan's equivalence, and compile A or B as not (not A and not B) though that has an even worse instruction count (4).

We might also consider whether we want to make these "greedy" as they are in Python. E.g., when A is True, A or B doesn't even evaluate B at all, since it can't affect the outcome. That actually yields a lower instruction count, with just one jump instruction for each disjunct (and perhaps another jump instruction or two to navigate past the if or the else clauses, depending on how efficiently we end up implementing those, the issue that started this thread, but those instructions should be counted as part of the if not part of the or).

Lonami commented 3 years ago

You're overthinking this. There is a bitwise OR in mlog, just not logical OR. It will evaluate all arguments (as opposed to Python's or, which is lazy), but it will do the trick.

If we want to do it lazily like Python (which we probably should, or it could break some people's expectations), we just need chained jumps.

if x == 1 or x == 2:
    y = 3

...becomes:

jump 4 eq x 1
jump 4 eq x 2
jump 5 always
set y 3
# end if
TelosTelos commented 3 years ago

Yep, your suggestion is exactly what I had in mind in the last paragraph above. Good to see we're on the same page!

There will still be a bit of trickiness if we're evaluating complex expressions (as I think we should) by using dummy variables to store intermediate results. With this approach to complex expressions, if A or B: would probably default to something that first sets dummy = A or B (computed however we end up deciding to compute that, perhaps greedily/lazily), and then performs if dummy:... It'd probably end up more efficient though to compile if A or B into something that doesn't use a dummy variable at all, like what you suggested above, which would be different from how X = A or B would compile.

Lonami commented 3 years ago

Implemented in https://github.com/Lonami/pyndustric/commit/f8441abbc059241ca3d224d7c0619aaa824384b8.