D.gnu - GDC generates invalid assembly around fiber yield operations (Not
- Liran Zvibel (91/91) May 03 2015 Hi,
- Liran Zvibel (30/30) May 05 2015 Hi,
- Liran Zvibel (12/103) May 14 2015 Hi,
- Johannes Pfau (28/42) May 14 2015 I can reproduce this using the latest master branch. This exact issue
- Johannes Pfau (9/10) May 15 2015 TLDR
- Iain Buclaw via D.gnu (4/14) May 15 2015 That is an interesting workaround, should we perhaps reconsider our
- Iain Buclaw via D.gnu (8/29) May 15 2015 I can confirm that C codegen does infact emit 'foo += bar()' as 'foo
- Johannes Pfau (6/43) May 15 2015 BTW: For commutative operations we can simply change the operands. For
- Liran Zvibel via D.gnu (4/42) May 15 2015 Please feel free to use my code sample in the test suite.
- Liran Zvibel via D.gnu (4/24) May 15 2015 Hi,
- ketmar (8/10) May 18 2015 this is not the case for `~=3D` (see [1]). yet i believe that there will...
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: 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 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 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
May 03 2015
Hi, Another thing that I noticed about this issue, is that in this isolated test case, the problem only happens if the added value is returned from the function that performs the 'yield()'. If I call 'yield()' directly, then add to 'globalSum', or if I just call 'otherFunc()', ignore the return value and then add to 'globalSum' then the result is correct. An example of run version that works: void run() { foreach(int k; 0 .. numAddition) { otherFunc(); globalSum += index; writefln("DerivedFiber(%d) iteration %d globalSum is %d", index, k, globalSum); } } An example of run version that breaks (exactly like the one from my previous mail): void run() { foreach(int k; 0 .. numAddition) { globalSum += otherFunc(); writefln("DerivedFiber(%d) iteration %d globalSum is %d", index, k, globalSum); } } Maybe this can help you understand something ... Thanks, Liran
May 05 2015
Hi, I forgot to mention is before, I'm running with GDC commit b022dd4cac195d85e9c3a6a37f2501a07ade455a from April 7th based on GCC 4.9. Does anyone have experience compiling (and then running) Fiber based applications with GDC? What are you doing? Is there a plan to add support for attribute("returns_twice") to GDC? Thanks! Liran On Sunday, 3 May 2015 at 18:45:36 UTC, Liran Zvibel wrote: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: 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 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 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
May 14 2015
Am Thu, 14 May 2015 15:52:05 +0000 schrieb "Liran Zvibel" <liran weka.io>:Hi, I forgot to mention is before, I'm running with GDC commit b022dd4cac195d85e9c3a6a37f2501a07ade455a from April 7th based on GCC 4.9. Does anyone have experience compiling (and then running) Fiber based applications with GDC? What are you doing? Is there a plan to add support for attribute("returns_twice") to GDC? Thanks!I can reproduce this using the latest master branch. This exact issue is kinda weird: GCC can't cache the globalSum across a call to otherFunc. otherFunc calls yield which is in a different module. yield could modify the globalSum value and the generated code would be invalid even without Fibers. Iain: I think our codegen might be wrong. -fdump-tree-original: 61 var_decl name: 76 mngl: 77 type: 60 scpe: 54 srcp: test.d:5 size: 39 algn: 64 used: 1 62 plus_expr type: 60 op 0: 61 op 1: 78 78 call_expr type: 60 fn : 96 0 : 23 Does GCC guarantee an evaluation order for plus_expr? This is kinda weird btw. The rewrite might already happen in the frontend. If we rewrite globalSum += otherFunc(); to globalSum = globalSum + otherFunc(); and follow strict LTR evaluation the code we generate is correct and the code DMD generates is incorrect. OTOH I don't know the exact rules for += but intuitively it should first evaluate the RHS, then load the LHS. Note: The issue the OP assumed is also worth discussing. It can't happen with global variables, but a second Fiber could modify a local variable. I'm not sure if it's possible to construct a case where GCC legitimately assumes a variable can't escape and we modify it through yield.
May 14 2015
Am Thu, 14 May 2015 19:02:48 +0200 schrieb Johannes Pfau <nospam example.com>:...TLDR As a workaround replace globalSum += otherFunc(); which GDC currently treats as globalSum = globalSum + otherFunc(); with globalSum = otherFunc() + globalSum;
May 15 2015
On 15 May 2015 at 09:08, Johannes Pfau via D.gnu <d.gnu puremagic.com> wrote:Am Thu, 14 May 2015 19:02:48 +0200 schrieb Johannes Pfau <nospam example.com>:That is an interesting workaround, should we perhaps reconsider our code generation here? Iain....TLDR As a workaround replace globalSum += otherFunc(); which GDC currently treats as globalSum = globalSum + otherFunc(); with globalSum = otherFunc() + globalSum;
May 15 2015
On 15 May 2015 at 09:13, Iain Buclaw <ibuclaw gdcproject.org> wrote:On 15 May 2015 at 09:08, Johannes Pfau via D.gnu <d.gnu puremagic.com> wrote:I can confirm that C codegen does infact emit 'foo += bar()' as 'foo = bar() + foo' Which only strengthens the reasoning to change it. Liran, can you raise a bug report? Also, can we use your small sample (names will be anonymised) to put into the testsuite? Regards Iain.Am Thu, 14 May 2015 19:02:48 +0200 schrieb Johannes Pfau <nospam example.com>:That is an interesting workaround, should we perhaps reconsider our code generation here?...TLDR As a workaround replace globalSum += otherFunc(); which GDC currently treats as globalSum = globalSum + otherFunc(); with globalSum = otherFunc() + globalSum;
May 15 2015
Am Fri, 15 May 2015 09:23:33 +0200 schrieb "Iain Buclaw via D.gnu" <d.gnu puremagic.com>:On 15 May 2015 at 09:13, Iain Buclaw <ibuclaw gdcproject.org> wrote:BTW: For commutative operations we can simply change the operands. For non-commutative operations we'll have to explicitly evaluate the side effects of the RHS before assigning. (-=, ...) Relevant code is in d-elem.cc: AddAssignExp::toElem etcOn 15 May 2015 at 09:08, Johannes Pfau via D.gnu <d.gnu puremagic.com> wrote:I can confirm that C codegen does infact emit 'foo += bar()' as 'foo = bar() + foo' Which only strengthens the reasoning to change it. Liran, can you raise a bug report? Also, can we use your small sample (names will be anonymised) to put into the testsuite? Regards Iain.Am Thu, 14 May 2015 19:02:48 +0200 schrieb Johannes Pfau <nospam example.com>:That is an interesting workaround, should we perhaps reconsider our code generation here?...TLDR As a workaround replace globalSum += otherFunc(); which GDC currently treats as globalSum = globalSum + otherFunc(); with globalSum = otherFunc() + globalSum;
May 15 2015
Please feel free to use my code sample in the test suite. I fill post a bug report. Thanks! LiranOn May 15, 2015, at 10:23, Iain Buclaw via D.gnu <d.gnu puremagic.com> wrote: On 15 May 2015 at 09:13, Iain Buclaw <ibuclaw gdcproject.org> wrote:On 15 May 2015 at 09:08, Johannes Pfau via D.gnu <d.gnu puremagic.com> wrote:I can confirm that C codegen does infact emit 'foo += bar()' as 'foo = bar() + foo' Which only strengthens the reasoning to change it. Liran, can you raise a bug report? Also, can we use your small sample (names will be anonymised) to put into the testsuite? Regards Iain.Am Thu, 14 May 2015 19:02:48 +0200 schrieb Johannes Pfau <nospam example.com>:That is an interesting workaround, should we perhaps reconsider our code generation here?...TLDR As a workaround replace globalSum += otherFunc(); which GDC currently treats as globalSum = globalSum + otherFunc(); with globalSum = otherFunc() + globalSum;
May 15 2015
Hi, I’m trying to port a 120k loc fiber based application from dmd to gdc, so applying this work around is not a good option for me (also, there are many engineers that will continue generating code…) Thanks LiranOn May 15, 2015, at 10:08, Johannes Pfau via D.gnu <d.gnu puremagic.com> wrote: Am Thu, 14 May 2015 19:02:48 +0200 schrieb Johannes Pfau <nospam example.com>:...TLDR As a workaround replace globalSum += otherFunc(); which GDC currently treats as globalSum = globalSum + otherFunc(); with globalSum = otherFunc() + globalSum;
May 15 2015
On Thu, 14 May 2015 19:02:48 +0200, Johannes Pfau wrote:OTOH I don't know the exact rules for +=3D but intuitively it should firs=tevaluate the RHS, then load the LHS.this is not the case for `~=3D` (see [1]). yet i believe that there will be= =20 myriads of reasons from DMD core team to decide that `+=3D` is very special= =20 (or `~=3D` is very special), and consistency sux. [1] https://issues.dlang.org/show_bug.cgi?id=3D13670=
May 18 2015