I have already reported this unexpected behavior in the mailing list.
I don't know which is the best place where to report this. Please close this issue if you think that the mailing list is the only place where to discuss this.
In my mini-project in: https://github.com/Fabrizio-Caruso/Primitive-Pythagorean-Triples
I am getting a faster binary by using "worse" source code.
I need to set an unsigned short with some computed value, but I get significantly faster code if I first store the value in a variable, i.e., something like this:
c1 = da+tc-db;
*(foo) = c1;
is faster than
*(foo) = da+tc-db;
Why is that possible? Is the optimizer missing an optimization with the code without the extra variable?
The actual code is in:
https://github.com/Fabrizio-Caruso/Primitive-Pythagorean-Triples/blob/master/PPT.c
where I select the two alternatives with:
#if defined(USE_MORE_VARIABLES)
a2 = a+db+dc;
a3 = a2-da;
b1 = da+dc-b;
b3 = (dc<<1)-b1;
c1 = da+tc-db;
c3 = s-da;
enqueue(a1); enqueue(b1); enqueue(c1);
enqueue(a2); enqueue(b1+db); enqueue(da+s);
enqueue(a3); enqueue(b3); enqueue(c3);
#else
a2 = a1 + (db<<1);
b1 = da+dc-b;
enqueue(a1); // a1
enqueue(b1); // b1
enqueue(da+tc-db); // c1
enqueue(a2); // a2
enqueue(b1+db); // b2
enqueue(da+s); // c2
enqueue(a2-da); // a3
enqueue((dc<<1)-b1); // b3
enqueue(s-da); // c3
#endif
where enqueue() is just a macro:
#define enqueue(item) \
*(tail++) = (number_t)(item)
Remark: I get the same unexpected speed-up even if I remove the ++ operator from the enqueue macro. The problem is not related to the ++ operator IMHO.
When you write
*(foo) = da+tc-db;
the compiler has to backup the left-hand-side (lhs) expression *(foo) before dealing with the right-hand-side (rhs) expression da+tc-db. Because the evaluation of the rhs could use up the very limited supply of registers and built-in ZP pseudo-registers, the lhs has to be pushed onto the cc65 stack as a backup, which is slow.
Note, it wouldn't help even if the compiler were changed to evaluate the rhs first and the lhs second, as the rhs would need to be backed up on the stack if so (the lhs can be a long pointer expression).
However, when you write
c1 = da+tc-db;
*(foo) = c1;
then the compiler can use c1 directly as the lhs on the first line, and no longer needs to back it up before evaluating da+tc-db. And when it is evaluating *(foo) on the second line, da+tc-db already is "backed up" in c1, and can be loaded back directly, too. Hence the improvement in performance.
Maybe we should document that compact expressions will not always lead to compact code?
@acqn Thanks for allow for explaining the reason of the problem.
For me this is not the expected behavior a C compiler should have, though. Clearly better written code should not produce worse binary in a very common situation such as an assignment. It is not a very rare situation where such unexpected behavior appears. Basically all assignments of this type should be split into two assignments and the user should know about it as this is not uncommon.
If we cannot easily fix this, we should document it to invite people to write such workarounds when using CC65.
I do not get this behavior on other 8-bit cross compilers (e.g., Z88DK) for the reasons you explained.
@acqn
Hmm, normally i would guess the compiler evaluates expressions according to the precedence and left right order. As according to the c precedence, the "+" and "-" has priority over "=" it would be expected that the rhs is evaluated first, then backed up, then the lhs is evaluated and then the backup is assigned to the lhs. I would make me really wonder if the lhs is evaluated first.
@Fabrizio-Caruso I was talking about the problem in general with some old impression. In fact, PR #1053 (some time after this issue) improved the optimizer dealing with storing via a pointer. So the performance could possibly have been increased already. You might be interested in testing the performance with the current revisions.
Hmm, normally i would guess the compiler evaluates expressions according to the precedence and left right order.
The C standard doesn't mandate the order. The C++ standard does, for "Right-to-Left", but I consider it _wrong_. Yes, I am one of the very few "Left-to-Right-Evaluation-Order-for-All" advocators. ;)
>
As according to the c precedence, the "+" and "-" has priority over "=" it would be expected that the rhs is evaluated first, then backed up, then the lhs is evaluated and then the backup is assigned to the lhs. I would make me really wonder if the lhs is evaluated first.
What about m[a + b] = c?
EDIT: whoops.
@acqn
I see your point: m[a +b] = c vs. c = m[a + b]. How should the compiler decide which is evaluated left and right and which right to left. Seems to be a matter of convention.