GDC generates invalid assembly around fiber yield operations (Not re-reading data from clobberedd memory to registers)
Liran Zvibel via D.gnu
d.gnu at puremagic.com
Sun May 3 11:45:35 PDT 2015
Hi,
We are trying to port a large fiber based application to GDC.
Our application works well when compiled with DMD with
optimizations.
It fails very quickly with GDC (even before we tried
optimizations), and we were able to narrow it to how GDC treats
yields.
Please look at the following small program (minor changes from
the core.thread.Fiber example):
import std.stdio;
import core.thread;
enum numAddition = 3;
long globalSum = 0;
class DerivedFiber : Fiber
{
int index;
this(int _index)
{
index = _index;
super( &run );
}
private :
void run()
{
foreach(int k; 0 .. numAddition) {
globalSum += otherFunc();
writefln("DerivedFiber(%d) iteration %d globalSum is
%d", index, k, globalSum);
}
}
long otherFunc() {
yield();
return index;
}
}
int main()
{
Fiber derived1 = new DerivedFiber(1);
Fiber derived2 = new DerivedFiber(2);
foreach(j; 0 .. (numAddition+1)) {
derived1.call();
derived2.call();
}
assert(globalSum == (1+2)*numAddition);
return 0;
}
And the following output when compiling first with DMD and then
with GDC:
bash-4.3# dmd -release -O fiber.d -ofdfb
bash-4.3# ./dfb
DerivedFiber(1) iteration 0 globalSum is 1
DerivedFiber(2) iteration 0 globalSum is 3
DerivedFiber(1) iteration 1 globalSum is 4
DerivedFiber(2) iteration 1 globalSum is 6
DerivedFiber(1) iteration 2 globalSum is 7
DerivedFiber(2) iteration 2 globalSum is 9
bash-4.3# /opt/gdc/bin/gdc -ggdb fiber.d -ogfb
bash-4.3# ./gfb
DerivedFiber(1) iteration 0 globalSum is 1
DerivedFiber(2) iteration 0 globalSum is 2
DerivedFiber(1) iteration 1 globalSum is 2
DerivedFiber(2) iteration 1 globalSum is 4
DerivedFiber(1) iteration 2 globalSum is 3
DerivedFiber(2) iteration 2 globalSum is 6
core.exception.AssertError at fiber.d(41): Assertion failure
[ Removed the very simple stack...]
When looking at the generated assembly, it's very easy to see
that the value of 'globalSum' is read to register before the call
to `otherFunc` (and also does not refresh it across the
'foreach'). The problem is that otherFunc calls 'yield' which
changes context and causes 'globalSum' to be updated.
GDC has to know that after `yield` is called memory is
potentially clobbered, and re-read memory back to registers.
I tried some things that did not help:
- declaring 'globalSum' as 'shared' or '__gshared'.
- declaring 'globalSum' as 'static' inside DerivedFiber, also
tried adding 'shared' or '__gshared'.
- adding 'asm {"nop;" : : : "memory" ;}' after the 'yield'.
- defining 'otherFunc' with @attribute("returns_twice") -- got
'error: unknown attribute returns_twice'.
Two things did work, but are unpractical when porting a big
application (over 100k loc) that heavily relies on Fibers:
- using a temporary variable to hold the 'otherFunc' return, then
add it to 'globalSum'
- calling core.atomic.otomicOp!"+="(globalSum, otherFunc())
We COULD identify all functions that immediately call 'yield' and
mark them with @attribute("returns_twice"), but that does not
seem to be supported by GDC.
Any ideas?
Thanks!
Liran
More information about the D.gnu
mailing list