Reduced test (with two selective imports).
---
struct Bytecode
{
uint data;
}
@trusted ctSub(U)(string format, U args)
{
import std.conv : to;
foreach (i; format)
return format~ to!string(args);
return format;
}
struct CtContext
{
import std.uni : CodepointSet;
CodepointSet[] charsets;
string ctAtomCode(Bytecode[] ir)
{
string code;
switch (code)
{
OrChar:
code ~= ``;
for (uint i ; i ;)
code ~= ctSub(``, ir[i].data);
charsets[ir[0].data].toSourceCode;
break;
default:
assert(0);
}
return code;
}
}
---
Causes link error.
---
backtracking.o: In function `_D3std4conv17__T6toImplTAyaTkZ6toImplFNaNbNekkE3std5ascii10LetterCaseZAya':
__main.d:(.text._D3std4conv17__T6toImplTAyaTkZ6toImplFNaNbNekkE3std5ascii10LetterCaseZAya+0x5e): undefined reference to `_D3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZS3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6Result'
backtracking.o: In function `_D3std5array96__T5arrayTS3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6ResultZ5arrayFNaNbNfS3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6ResultZAa':
__main.d:(.text._D3std5array96__T5arrayTS3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6ResultZ5arrayFNaNbNfS3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6ResultZAa+0xd): undefined reference to `_D3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6Result6lengthMFNaNbNdNiNfZm'
__main.d:(.text._D3std5array96__T5arrayTS3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6ResultZ5arrayFNaNbNfS3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6ResultZAa+0x4f): undefined reference to `_D3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6Result5emptyMFNaNbNdNiNfZb'
__main.d:(.text._D3std5array96__T5arrayTS3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6ResultZ5arrayFNaNbNfS3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6ResultZAa+0x5c): undefined reference to `_D3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6Result5frontMFNaNbNdNiNfZa'
__main.d:(.text._D3std5array96__T5arrayTS3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6ResultZ5arrayFNaNbNfS3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6ResultZAa+0x9f): undefined reference to `_D3std4conv47__T7toCharsVii10TaVE3std5ascii10LetterCasei1TkZ7toCharsFNaNbNiNfkZ6Result8popFrontMFNaNbNiNfZv'
collect2: error: ld returned 1 exit status
Error: linker exited with status 1
---
Prettified.
---
backtracking.o: In function `std.conv.toImpl!(immutable(char)[], uint).toImpl(uint, uint, std.ascii.LetterCase)':
__main.d:(0x5e): undefined reference to `std.conv.toChars!(10, char, 1, uint).toChars(uint)'
backtracking.o: In function `std.array.array!(std.conv.toChars!(10, char, 1, uint).toChars(uint).Result).array(std.conv.toChars!(10, char, 1, uint).toChars(uint).Result)':
__main.d:(0xd): undefined reference to `std.conv.toChars!(10, char, 1, uint).toChars(uint).Result.length()'
__main.d:(0x4f): undefined reference to `std.conv.toChars!(10, char, 1, uint).toChars(uint).Result.empty()'
__main.d:(0x5c): undefined reference to `std.conv.toChars!(10, char, 1, uint).toChars(uint).Result.front()'
__main.d:(0x9f): undefined reference to `std.conv.toChars!(10, char, 1, uint).toChars(uint).Result.popFront()'
collect2: error: ld returned 1 exit status
Error: linker exited with status 1
---
Does not happen with 2.073.
---
$ dmd-2073 backtracking.d -v 2>&1 | grep toChars.Result.popFront
function std.conv.toChars!(10, char, cast(LetterCase)true, uint).toChars.Result.popFront
function std.conv.toChars!(16, char, cast(LetterCase)false, uint).toChars.Result.popFront
function std.conv.toChars!(16, char, cast(LetterCase)true, uint).toChars.Result.popFront
function std.conv.toChars!(2, char, cast(LetterCase)true, uint).toChars.Result.popFront
function std.conv.toChars!(8, char, cast(LetterCase)true, uint).toChars.Result.popFront
$ dmd-2074 backtracking.d -v 2>&1 | grep toChars.Result.popFront
function std.conv.toChars!(16, char, cast(LetterCase)false, uint).toChars.Result.popFront
function std.conv.toChars!(16, char, cast(LetterCase)true, uint).toChars.Result.popFront
function std.conv.toChars!(2, char, cast(LetterCase)true, uint).toChars.Result.popFront
function std.conv.toChars!(8, char, cast(LetterCase)true, uint).toChars.Result.popFront
---
However when testing with gdc, all I updated was the library, and managed to reproduce the same thing, so a change in phobos exposed this.
Comment #2 by petar.p.kirov — 2017-08-03T07:43:26Z
Definitely looks like a front-end bug to me.
Comment #3 by ibuclaw — 2017-08-03T08:47:34Z
(In reply to ZombineDev from comment #2)
> Definitely looks like a front-end bug to me.
The link error does not happen when compiling the test with -unittest.
So the specific location to look would be file dtemplate.d, function appendToModuleMember.
The change in phobos was enough to make the instantiation speculative, so its arguable who's at fault. The compiler may be working as intended, though admittedly the whole design around speculative templates is not really backed by any spec.
Comment #4 by kinke — 2017-08-03T14:09:28Z
LDC ran into the same issue when moving to Phobos 2.074. I ended up switching from individual druntime/Phobos object file compilation to all-D-files-at-once compilation (like DMD), as that reduces the template instantiation culling [1]. See LDC PR [2].
This template culling, in combination with restructured Phobos, also leads to sometimes dramatic performance decreases due to less inlining potential with non-instantiated (culled) templates, rendering cross-module-inlining/LTO essential for best performance. See this (lenghty but interesting) LDC issue [3]. There's a link to a Weka.io-specific patch where they had to disable the culling due to linker errors.
For LDC, I'm planning to allow the user to prevent template culling via a command-line switch, as the current implementation doesn't seem very mature.
[1]: https://github.com/dlang/dmd/blob/v2.075.0/src/ddmd/dtemplate.d#L7197-L7205
[2]: https://github.com/ldc-developers/ldc/pull/2076#issuecomment-315175464
[3]: https://github.com/ldc-developers/ldc/issues/2168
Comment #5 by petar.p.kirov — 2017-08-03T14:15:43Z
> For LDC, I'm planning to allow the user to prevent template culling via a command-line switch, as the current implementation doesn't seem very mature.
I think that's what dmd's -allinst switch does currently.
Comment #6 by kinke — 2017-08-03T14:33:23Z
(In reply to ZombineDev from comment #5)
> > For LDC, I'm planning to allow the user to prevent template culling via a command-line switch, as the current implementation doesn't seem very mature.
>
> I think that's what dmd's -allinst switch does currently.
[LDC uses the same logic and command-line switch.] I find it quite hard to tell the exact difference when using `-allinst` (and whether that includes speculative ones), as it's an early return of that function and the remaining needsCodegen() logic isn't trivial; the interaction with the tnext chain doesn't make it any simpler unfortunately.
Comment #7 by johannespfau — 2017-08-03T14:40:24Z
For reference: the missing symbol also causes a backend ICE for GCC <= 4.9:
https://bugzilla.gdcproject.org/show_bug.cgi?id=157
This is fortunately of lower priority as an (unrelated) GCC change works around this problem in all new GCC versions. Fortunately we're already at GCC-7 released / GCC-8 in development :-)
*** Issue 18234 has been marked as a duplicate of this issue. ***
Comment #10 by schveiguy — 2018-06-11T19:06:36Z
(In reply to Iain Buclaw from comment #3)
> The link error does not happen when compiling the test with -unittest.
I just want to point out that using -unittest currently is the same as using -allinst.
Comment #11 by b2.temp — 2018-06-12T19:02:40Z
interesting fact:
The problem doesn't happen when std.conv.toChars template parameter `radix` is changed from `ubyte` to `int` or when it changed to an `alias radix = 10`
Comment #12 by b2.temp — 2018-06-12T20:03:45Z
speculative could fail because it wrongly thinks that it will ALREADY be codegened.
in toImpl (line 1394 of std.conv) we have
---
return toChars!(10, EEType)(value + 0).array;
---
but if we specifies the type of value:
---
return toChars!(10, EEType, LetterCase.lower, typeof(value + 0))(value + 0).array;
---
than the test case passes. Maybe IFTI fails/is wrong with "value + 0", trick to promote) and then it gives another template that is HIM codegened.
Comment #13 by b2.temp — 2018-06-13T07:18:58Z
Other test case:
---
import std.datetime;
import std.typecons;
import std.variant;
Y a()
{
Y n = Y(Y[].init);
n.get!(X[]);
return n;
}
struct X
{
Y key;
}
struct Y
{
Algebraic!(Y[]) value_;
this(T)(T value)
{
value_ = value;
}
bool opEquals(T)(T rhs) const
{
static if(is(Unqual!T == Y))
{
return true;
}
else
{
return get!(T, No.x) == get!T;
}
}
T get(T, Flag!"x" x = Yes.x)() const
{
return this[""].get!(T, x);
}
Y opIndex(T)(T index) const
{
const X[] pairs;
if(pairs[0].key == index)
{
assert(0);
}
assert(0);
}
}
void main(){}
---
linker error without -allinst
Comment #14 by slavo5150 — 2018-06-16T11:25:56Z
This bug would be much easier to troubleshoot if it could be reduced to a couple of modules and *not* import any Phobos module. A -betterC reproduction would be even better. There's just too much noise when parsing and semantic-processing Phobos modules to see what's wrong and where.
Comment #15 by ibuclaw — 2018-07-02T21:22:32Z
Still reproducible on 2.081 release candidate (I've had to revert the commit again).
Comment #16 by github-bugzilla — 2018-09-07T20:20:16Z
I'm not too happy about closing this, as the underlying issue definitely isn't fixed. I bet https://github.com/ldc-developers/ldc/issues/2846 (not LDC-specific) is yet another instance of this template culling issue.
Comment #18 by b2.temp — 2018-09-08T02:27:12Z
How hard would it be to have a @allinst function attribute, or better, a pragma(allinst) ?
* -allinst is globally applied so not a ideal workaround.
* workarounds like the one that's been merged can't be done each time the problem is encountered in phobos.
Comment #19 by nick — 2020-06-19T11:35:26Z
(In reply to kinke from comment #17)
> I'm not too happy about closing this, as the underlying issue definitely
> isn't fixed.
Yes, and the workaround didn't even add a test case to prevent someone just changing std.conv.text to use toChars again. The toChars radix workaround (comment #11) would have had much lower impact. I haven't been able to reproduce the original bug with dmd v2.092.0, maybe I'm doing something wrong though.
Comment #20 by dlang-bot — 2020-06-19T16:30:19Z
@ntrel created dlang/dmd pull request #11300 "Fix Issue #17712 - Add test cases" fixing this issue:
- Fix Issue #17712 - Add test cases
https://github.com/dlang/dmd/pull/11300
Comment #21 by moonlightsentinel — 2020-06-22T01:58:25Z
Reduced test case without phobos that still fails with current dmd:
=============================================
// stdx/conv.d:
void to(T)(uint args)
{
toChars!10(args);
}
alias CodepointSet = InversionList!();
struct InversionList()
{
void toSourceCode()
{
toChars(0u);
}
}
void toChars(ubyte radix = 10, T)(T) {}
=============================================
// test.d:
void main()
{
import stdx.conv;
to!string(0u);
CodepointSet().toSourceCode();
}
=============================================
dmd -c stdx/conv.d
dmd test.d conv.o
Comment #22 by razvan.nitu1305 — 2023-04-10T09:57:45Z
(In reply to moonlightsentinel from comment #21)
> Reduced test case without phobos that still fails with current dmd:
>
> =============================================
> // stdx/conv.d:
>
> void to(T)(uint args)
> {
> toChars!10(args);
> }
>
> alias CodepointSet = InversionList!();
>
> struct InversionList()
> {
> void toSourceCode()
> {
> toChars(0u);
> }
> }
>
> void toChars(ubyte radix = 10, T)(T) {}
>
> =============================================
> // test.d:
>
> void main()
> {
> import stdx.conv;
> to!string(0u);
> CodepointSet().toSourceCode();
> }
> =============================================
>
> dmd -c stdx/conv.d
> dmd test.d conv.o
I cannot reproduce this.