Thread overview
segfault in ldc release only - looks like some kind of optimization bug?
Jul 23, 2019
aliak
Jul 23, 2019
Exil
Jul 23, 2019
aliak
Jul 23, 2019
Exil
July 23, 2019
Hi,

so I had this weird bug that was driving me crazy and only segfaulted with ldc in release build - (I'm using ldc 1.16.0).

This is the code that segfaults. All parts seem to be necessary for it to happen, or at least I think so. I've gone in circles minimizing so I've probably missed something. But this seems to be it:

import std;

struct W(T) {
    T value;
    ref inout(T) front() inout { return value; }
}

auto ref get(T)(W!T value) {
    return value.front;
}

struct S {
    string a;
}

void main() {
    S[string] aa;
    aa = ["one" : S("a")];
    auto b = W!S(aa["one"]).get;
    writeln(b);
}

Running with ldc and -O3 crashes: "ldc2 -O3 -run source.d"

Some things I've noticed:
- if you remove the call to .get and use .value directly, the crash goes
- if you remove the inout specifier on front(), the crash goes
- if you remove the ref specifier on front(), the crash goes
- if you don't call writeln(b), the crash goes
- if you don't use the s returned by the aa, the crash goes
- if you *add* a return qualifier on the "front" function, the crash goes away

(what is that return thing btw and when do you use it?)

Any ideas?

July 23, 2019
auto ref get(T)(W!T value) {
    return value.front;
}

You're returning a reference to a temporary that gets deleted at the end of the function's scope. The "auto ref" here will be a "ref".
July 23, 2019
On Tuesday, 23 July 2019 at 00:36:49 UTC, Exil wrote:
> auto ref get(T)(W!T value) {
>     return value.front;
> }
>
> You're returning a reference to a temporary that gets deleted at the end of the function's scope. The "auto ref" here will be a "ref".

..... oh ... shit.... you're right.

Ok so this was minimized from this:

const config = Config.ghApp(ghDomain)
        .orElseThrow!(() => new Exception(
            "could not find config for domain '%s'".format(ghDomain)
        ));

Where Config.ghApp return an Optional!GhApp, and orElseThrow checks if a range has is not empty and returns front. The front in Optional is defined as the front above...

So is that an incorrect idiom to use when writing a library then? I pretty sure I've seen it in phobos too.

Slapping return on the function also fixes it. Is that the correct way to write a .front?

Thanks!
July 23, 2019
On Tuesday, 23 July 2019 at 00:54:08 UTC, aliak wrote:
> On Tuesday, 23 July 2019 at 00:36:49 UTC, Exil wrote:
>> auto ref get(T)(W!T value) {
>>     return value.front;
>> }
>>
>> You're returning a reference to a temporary that gets deleted at the end of the function's scope. The "auto ref" here will be a "ref".
>
> ..... oh ... shit.... you're right.
>
> Ok so this was minimized from this:
>
> const config = Config.ghApp(ghDomain)
>         .orElseThrow!(() => new Exception(
>             "could not find config for domain '%s'".format(ghDomain)
>         ));
>
> Where Config.ghApp return an Optional!GhApp, and orElseThrow checks if a range has is not empty and returns front. The front in Optional is defined as the front above...
>
> So is that an incorrect idiom to use when writing a library then? I pretty sure I've seen it in phobos too.
>
> Slapping return on the function also fixes it. Is that the correct way to write a .front?
>
> Thanks!

Yes you can use "return". It basically tells the compiler that the function or method returns something that is referenced by a passed in parameter so to keep it alive.

https://dlang.org/spec/function.html#return-ref-parameters

Your orElseThrow() probably shouldn't be taking in a copy though. That's the catch, then you can't use it in a UFCS chain like you are using now. Using "return" is not an ideal fix, as it is still going to be calling the destructor on the object. So you might run into an issue somewhere. It just so happens to generate assembly that works though I guess when there is no destructor.