Jump to page: 1 2
Thread overview
[Issue 18058] @nogc and forwarding lazy argument, particularly with scope
Dec 11, 2017
Maksim Fomin
Dec 11, 2017
Seb
Dec 12, 2017
Shachar Shemesh
Dec 12, 2017
Shachar Shemesh
Dec 12, 2017
Maksim Fomin
Dec 12, 2017
Maksim Fomin
Dec 12, 2017
Shachar Shemesh
Dec 12, 2017
Maksim Fomin
Dec 12, 2017
Shachar Shemesh
Dec 17, 2022
Iain Buclaw
December 11, 2017
https://issues.dlang.org/show_bug.cgi?id=18058

Maksim Fomin <mxfm@protonmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |mxfm@protonmail.com

--- Comment #1 from Maksim Fomin <mxfm@protonmail.com> ---
Since scope and lazy are poorly specified, it is not surprising that sometime they behave unexpectedly (and in my experience scope and lazy features are those which are mostly overlooked). Worse, because of underspecification it is often unclear whether issue is bug or not.

Compiler seems to fail here: https://github.com/dlang/dmd/blob/6f99996ba80ad51b37401552be4b3ace7f8e710b/src/ddmd/expressionsem.d#L3481

if (!tf.isnogc && sc.func.setGC())
{
   exp.error("@nogc %s '%s' cannot call non-@nogc %s '%s'",
      sc.func.kind(), sc.func.toPrettyChars(), p, exp.e1.toChars());
   err = true;
}

i.e. because 'func2' formally requires gc.

P.S. Unfortunatelly d developers put almost all efforts to fixing regressions plus some bugs and leave documentation as it is. The situation has not improved for many years.

> This is wrong. Since "msg" is never evaluated, the fact that it is not @nogc should not matter.

Why? Accordging to the spec closure must be always allocated regarless of scope (of course, this can be proposed as an improvement).

> The second problem is that the lowering is overly complicated. Instead of lowering to:

Again, currently compiler behaves according to the spec (in my understanding). If you prefer option 1 you can write it explicitly.

--
December 11, 2017
https://issues.dlang.org/show_bug.cgi?id=18058

Seb <greensunny12@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |greensunny12@gmail.com

--- Comment #2 from Seb <greensunny12@gmail.com> ---
FWIW that attributes are set wrongly on lazy parameters and their function is a well-known bug and has been around for a long time. See e.g. issue 12647 (there's quite a list of duplicates attached to it). There even have been two PRs (see issue 12647 for a list) aimed at fixing them, but they didn't tackle the root problem.


The problem becomes easy to inspect if one uses the hidden -vcg-ast flag at the compiler.

For the example linked at

--
import object;
void assertThrown(E)(lazy E expression)
{
        expression();
}
void main()
{
        assertThrown(delegate int() => 0);
        return 0;
}
assertThrown!int
{
        pure @safe void assertThrown(lazy int expression) // <- should have
nothrow
        {
                expression();
        }

}
---

For yours:

---
import object;
@nogc void func1(lazy scope string msg)
{
}
void func2(lazy scope string msg)
{
        func1(delegate string() => msg()); // <- lambda doesn't have @nogc
}
---

--
December 12, 2017
https://issues.dlang.org/show_bug.cgi?id=18058

--- Comment #3 from Shachar Shemesh <shachar@weka.io> ---
(In reply to Maksim Fomin from comment #1)
> > The second problem is that the lowering is overly complicated. Instead of lowering to:
> 
> Again, currently compiler behaves according to the spec (in my understanding). If you prefer option 1 you can write it explicitly.

No, I cannot. "lazy" and "delegate" behave the same only internally. As far as syntax goes, I cannot get the delegate that a lazy has turned into, nor can I can pass a delegate where a function expects a lazy argument.

--
December 12, 2017
https://issues.dlang.org/show_bug.cgi?id=18058

--- Comment #4 from Shachar Shemesh <shachar@weka.io> ---
(In reply to Seb from comment #2)
> FWIW that attributes are set wrongly on lazy parameters and their function is a well-known bug and has been around for a long time.

True, but it wouldn't be a problem had the overly complex forwarding not take place.
> 
> For yours:
> 
> ---
> import object;
> @nogc void func1(lazy scope string msg)
> {
> }
> void func2(lazy scope string msg)
> {
> 	func1(delegate string() => msg()); // <- lambda doesn't have @nogc
> }
> ---

The problem is not that the lambda doesn't have @nogc. The problem is that if we're going to provide a lambda there, it has no choice *but* to GC. The lambda has to have access to the frame (because "scope" was not honored), and so must allocate.

The issue here is that, once you've started lowering, the code the compiler emits is way under-optimized. It essentially creates a lambda whose sole purpose is evaluating another delegate that has the precise same interface. Had that redundant delegate not been generated, there would not be a need to allocate func2's frame with a GC, even without scope on the lazy.

--
December 12, 2017
https://issues.dlang.org/show_bug.cgi?id=18058

--- Comment #5 from Maksim Fomin <mxfm@protonmail.com> ---
(In reply to Shachar Shemesh from comment #3)
> (In reply to Maksim Fomin from comment #1)
> > > The second problem is that the lowering is overly complicated. Instead of lowering to:
> > 
> > Again, currently compiler behaves according to the spec (in my understanding). If you prefer option 1 you can write it explicitly.
> 
> No, I cannot. "lazy" and "delegate" behave the same only internally. As far as syntax goes, I cannot get the delegate that a lazy has turned into, nor can I can pass a delegate where a function expects a lazy argument.

I meant rewriting in another syntax explicitly.

--
December 12, 2017
https://issues.dlang.org/show_bug.cgi?id=18058

--- Comment #6 from Maksim Fomin <mxfm@protonmail.com> ---
(In reply to Shachar Shemesh from comment #4)
> (In reply to Seb from comment #2)
> > FWIW that attributes are set wrongly on lazy parameters and their function is a well-known bug and has been around for a long time.
> 
> True, but it wouldn't be a problem had the overly complex forwarding not take place.
> > 
> > For yours:
> > 
> > ---
> > import object;
> > @nogc void func1(lazy scope string msg)
> > {
> > }
> > void func2(lazy scope string msg)
> > {
> > 	func1(delegate string() => msg()); // <- lambda doesn't have @nogc
> > }
> > ---
> 
> The problem is not that the lambda doesn't have @nogc. The problem is that if we're going to provide a lambda there, it has no choice *but* to GC. The lambda has to have access to the frame (because "scope" was not honored), and so must allocate.

Exactly. This is how D works: some feature is lowered to code and if lowered code does not work, than the original code must be rewritten. Yes, compiler errs strangely as was mentioned in the forum. I am not aware of any example (except UFCS which is different case) where compiler is smart enough to know in advance than lowered code will not work, but compiler tries to rewrite in other way because it can work (i would call such behavior 'semantic optimization').

> The issue here is that, once you've started lowering, the code the compiler emits is way under-optimized. It essentially creates a lambda whose sole purpose is evaluating another delegate that has the precise same interface. Had that redundant delegate not been generated, there would not be a need to allocate func2's frame with a GC, even without scope on the lazy.

As mentioned previously, this is how D works. Optimization refers to code optimization and this stage happens after semantic analysis.

--
December 12, 2017
https://issues.dlang.org/show_bug.cgi?id=18058

--- Comment #7 from Shachar Shemesh <shachar@weka.io> ---
(In reply to Maksim Fomin from comment #6)
> As mentioned previously, this is how D works.

Quite so. That's why the label on this page reads "bug".

--
December 12, 2017
https://issues.dlang.org/show_bug.cgi?id=18058

Maksim Fomin <mxfm@protonmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Severity|major                       |normal

--- Comment #8 from Maksim Fomin <mxfm@protonmail.com> ---
(In reply to Shachar Shemesh from comment #7)
> (In reply to Maksim Fomin from comment #6)
> > As mentioned previously, this is how D works.
> 
> Quite so. That's why the label on this page reads "bug".

I mean dmd works as intended and according to the spec. What you want is outside of current compiler optimization. This does not prevent you from requesting new behavior as an enhancement.

--
December 12, 2017
https://issues.dlang.org/show_bug.cgi?id=18058

--- Comment #9 from Shachar Shemesh <shachar@weka.io> ---
(In reply to Maksim Fomin from comment #8)
> I mean dmd works as intended and according to the spec. What you want is outside of current compiler optimization. This does not prevent you from requesting new behavior as an enhancement.

Feel free to modify the category this bug is filed under to the one that is applicable to bugs in the spec.

Hint: it already is.

--
November 01, 2020
https://issues.dlang.org/show_bug.cgi?id=18058

johanengelen@weka.io changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Keywords|                            |industry
                 CC|                            |johanengelen@weka.io

--
« First   ‹ Prev
1 2