c++: Make explicit instantiations not vague linkage
Checks
Context |
Check |
Description |
linaro-tcwg-bot/tcwg_gcc_build--master-arm |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 |
success
|
Build passed
|
Commit Message
I discovered from some further testing that I broke 'import std' in some
cases with my last patch; this fixes that.
This still isn't sufficient I've found to fix PR119154 completely, as
there's still more cases where this assert fires due to performing
import_export_decl on non-DECL_REALLY_EXTERN at EOF. I'll continue
trying to reduce and find each case.
But either way I think this is a needed improvement; bootstrapped and
regtested on x86_64-pc-linux-gnu (so far just modules.exp), OK for trunk
if full regtest succeeds?
-- >8 --
My change in r15-8012 for PR c++/119154 caused a bug with explicit
instantation declarations. The change cleared DECL_INTERFACE_KNOWN for
all vague-linkage entities, including explicit instantiations. When we
then perform lazy loading at EOF (due to processing deferred function
bodies), expand_or_defer_fn ends up calling import_export_decl which
will error because DECL_INTERFACE_KNOWN is still unset but no definition
is available in the file, violating some assertions.
It turns out that for function templates marked inline we would not
respect an 'extern template' imported in general, either; this patch
fixes both of these issues by always treating explicit instantiations as
external, and so marking DECL_INTERFACE_KNOWN eagerly.
For an explicit instantiation declaration we don't want to emit the body
of the function as it must be emitted in a different TU anyway. And for
explicit instantiation definitions we similarly know that it will have
been emitted in the interface TU we streamed it in from, so there's
no need to emit it.
gcc/cp/ChangeLog:
* decl2.cc (vague_linkage_p): Explicit instantiations are not
vague linkage.
gcc/testsuite/ChangeLog:
* g++.dg/modules/extern-tpl-3_a.C: New test.
* g++.dg/modules/extern-tpl-3_b.C: New test.
* g++.dg/modules/extern-tpl-4_a.C: New test.
* g++.dg/modules/extern-tpl-4_b.C: New test.
Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
gcc/cp/decl2.cc | 3 ++
gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C | 11 +++++
gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C | 12 +++++
gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C | 24 ++++++++++
gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C | 46 +++++++++++++++++++
5 files changed, 96 insertions(+)
create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C
create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C
create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C
create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C
Comments
On 3/13/25 11:16 AM, Nathaniel Shead wrote:
> I discovered from some further testing that I broke 'import std' in some
> cases with my last patch; this fixes that.
>
> This still isn't sufficient I've found to fix PR119154 completely, as
> there's still more cases where this assert fires due to performing
> import_export_decl on non-DECL_REALLY_EXTERN at EOF. I'll continue
> trying to reduce and find each case.
>
> But either way I think this is a needed improvement; bootstrapped and
> regtested on x86_64-pc-linux-gnu (so far just modules.exp), OK for trunk
> if full regtest succeeds?
>
> -- >8 --
>
> My change in r15-8012 for PR c++/119154 caused a bug with explicit
> instantation declarations. The change cleared DECL_INTERFACE_KNOWN for
> all vague-linkage entities, including explicit instantiations. When we
> then perform lazy loading at EOF (due to processing deferred function
> bodies), expand_or_defer_fn ends up calling import_export_decl which
> will error because DECL_INTERFACE_KNOWN is still unset but no definition
> is available in the file, violating some assertions.
>
> It turns out that for function templates marked inline we would not
> respect an 'extern template' imported in general, either; this patch
> fixes both of these issues by always treating explicit instantiations as
> external, and so marking DECL_INTERFACE_KNOWN eagerly.
>
> For an explicit instantiation declaration we don't want to emit the body
> of the function as it must be emitted in a different TU anyway. And for
> explicit instantiation definitions we similarly know that it will have
> been emitted in the interface TU we streamed it in from, so there's
> no need to emit it.
>
> gcc/cp/ChangeLog:
>
> * decl2.cc (vague_linkage_p): Explicit instantiations are not
> vague linkage.
This seems like a big hammer for fixing a modules-specific issue; let's
address it in the modules code, not here.
> gcc/testsuite/ChangeLog:
>
> * g++.dg/modules/extern-tpl-3_a.C: New test.
> * g++.dg/modules/extern-tpl-3_b.C: New test.
> * g++.dg/modules/extern-tpl-4_a.C: New test.
> * g++.dg/modules/extern-tpl-4_b.C: New test.
>
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
> gcc/cp/decl2.cc | 3 ++
> gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C | 11 +++++
> gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C | 12 +++++
> gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C | 24 ++++++++++
> gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C | 46 +++++++++++++++++++
> 5 files changed, 96 insertions(+)
> create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C
> create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C
> create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C
> create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C
>
> diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
> index 4a9fb1c3c00..712fdc45d40 100644
> --- a/gcc/cp/decl2.cc
> +++ b/gcc/cp/decl2.cc
> @@ -2480,6 +2480,9 @@ vague_linkage_p (tree decl)
> /* Unfortunately, import_export_decl has not always been called
> before the function is processed, so we cannot simply check
> DECL_COMDAT. */
> + if (DECL_LANG_SPECIFIC (decl)
> + && DECL_EXPLICIT_INSTANTIATION (decl))
> + return false;
> if (DECL_COMDAT (decl)
> || (TREE_CODE (decl) == FUNCTION_DECL
> && DECL_DECLARED_INLINE_P (decl)
> diff --git a/gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C b/gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C
> new file mode 100644
> index 00000000000..def3cd1413d
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C
> @@ -0,0 +1,11 @@
> +// { dg-additional-options "-fmodules -Wno-global-module" }
> +// { dg-module-cmi M }
> +
> +module;
> +template <typename>
> +struct S {
> + S() {}
> +};
> +export module M;
> +extern template class S<int>;
> +S<int> s;
> diff --git a/gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C b/gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C
> new file mode 100644
> index 00000000000..5d96937ce02
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C
> @@ -0,0 +1,12 @@
> +// { dg-additional-options "-fmodules" }
> +
> +template <typename>
> +struct S {
> + S() {}
> +};
> +
> +void foo() { S<double> x;}
> +
> +import M;
> +
> +// Lazy loading of extern S<int> at EOF should not ICE
> diff --git a/gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C b/gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C
> new file mode 100644
> index 00000000000..16f1b041307
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C
> @@ -0,0 +1,24 @@
> +// { dg-additional-options "-fmodules" }
> +// { dg-module-cmi M }
> +
> +export module M;
> +
> +export template <typename T> inline void a() {}
> +extern template void a<int>();
> +extern template void a<bool>();
> +template void a<char>();
> +
> +export template <typename T> void b() {}
> +extern template void b<int>();
> +extern template void b<bool>();
> +template void b<char>();
> +
> +export template <typename T> inline int c = 123;
> +extern template int c<int>;
> +extern template int c<bool>;
> +template int c<char>;
> +
> +export template <typename T> int d = 123;
> +extern template int d<int>;
> +extern template int d<bool>;
> +template int d<char>;
> diff --git a/gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C b/gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C
> new file mode 100644
> index 00000000000..1dd4afe9f6b
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C
> @@ -0,0 +1,46 @@
> +// { dg-additional-options "-fmodules" }
> +
> +import M;
> +
> +int main() {
> + a<int>();
> + a<char>();
> + a<double>();
> +
> + b<int>();
> + b<char>();
> + b<double>();
> +
> + int x = c<int> + c<char> + c<double>;
> + int y = d<int> + d<char> + d<double>;
> + return x + y;
> +}
> +
> +// 'int': imported explicit instantiation decls should not be emitted here:
> +// { dg-final { scan-assembler-not "_ZW1M1aIiEvv:" } }
> +// { dg-final { scan-assembler-not "_ZW1M1bIiEvv:" } }
> +// { dg-final { scan-assembler-not "_ZW1M1cIiE:" } }
> +// { dg-final { scan-assembler-not "_ZW1M1dIiE:" } }
> +
> +// 'char': explicit instantiation definitions don't need to be emitted here:
> +// { dg-final { scan-assembler-not "_ZW1M1aIcEvv:" } }
> +// { dg-final { scan-assembler-not "_ZW1M1bIcEvv:" } }
> +// { dg-final { scan-assembler-not "_ZW1M1cIcE:" } }
> +// { dg-final { scan-assembler-not "_ZW1M1dIcE:" } }
> +
> +// 'double': these are not explicitly instantiated and should be emitted here:
> +// { dg-final { scan-assembler "_ZW1M1aIdEvv:" } }
> +// { dg-final { scan-assembler "_ZW1M1bIdEvv:" } }
> +// { dg-final { scan-assembler "_ZW1M1cIdE:" } }
> +// { dg-final { scan-assembler "_ZW1M1dIdE:" } }
> +
> +template void a<bool>();
> +template void b<bool>();
> +template int c<bool>;
> +template int d<bool>;
> +
> +// 'bool': instantiated in this file, and so must be emitted here:
> +// { dg-final { scan-assembler "_ZW1M1aIbEvv:" } }
> +// { dg-final { scan-assembler "_ZW1M1bIbEvv:" } }
> +// { dg-final { scan-assembler "_ZW1M1cIbE:" } }
> +// { dg-final { scan-assembler "_ZW1M1dIbE:" } }
On Thu, Mar 13, 2025 at 01:37:37PM -0400, Jason Merrill wrote:
> On 3/13/25 11:16 AM, Nathaniel Shead wrote:
> > I discovered from some further testing that I broke 'import std' in some
> > cases with my last patch; this fixes that.
> >
> > This still isn't sufficient I've found to fix PR119154 completely, as
> > there's still more cases where this assert fires due to performing
> > import_export_decl on non-DECL_REALLY_EXTERN at EOF. I'll continue
> > trying to reduce and find each case.
> >
> > But either way I think this is a needed improvement; bootstrapped and
> > regtested on x86_64-pc-linux-gnu (so far just modules.exp), OK for trunk
> > if full regtest succeeds?
> >
> > -- >8 --
> >
> > My change in r15-8012 for PR c++/119154 caused a bug with explicit
> > instantation declarations. The change cleared DECL_INTERFACE_KNOWN for
> > all vague-linkage entities, including explicit instantiations. When we
> > then perform lazy loading at EOF (due to processing deferred function
> > bodies), expand_or_defer_fn ends up calling import_export_decl which
> > will error because DECL_INTERFACE_KNOWN is still unset but no definition
> > is available in the file, violating some assertions.
> >
> > It turns out that for function templates marked inline we would not
> > respect an 'extern template' imported in general, either; this patch
> > fixes both of these issues by always treating explicit instantiations as
> > external, and so marking DECL_INTERFACE_KNOWN eagerly.
> >
> > For an explicit instantiation declaration we don't want to emit the body
> > of the function as it must be emitted in a different TU anyway. And for
> > explicit instantiation definitions we similarly know that it will have
> > been emitted in the interface TU we streamed it in from, so there's
> > no need to emit it.
> >
> > gcc/cp/ChangeLog:
> >
> > * decl2.cc (vague_linkage_p): Explicit instantiations are not
> > vague linkage.
>
> This seems like a big hammer for fixing a modules-specific issue; let's
> address it in the modules code, not here.
Fair enough, like this then?
Tested modules.exp so far, OK for trunk if full bootstrap and regtest
succeeds?
-- >8 --
Subject: [PATCH] c++/modules: Always treat explicit instantiations as external
My change in r15-8012 for PR c++/119154 caused a bug with explicit
instantation declarations. The change cleared DECL_INTERFACE_KNOWN for
all vague-linkage entities, including explicit instantiations. When we
then perform lazy loading at EOF (due to processing deferred function
bodies), expand_or_defer_fn ends up calling import_export_decl which
will error because DECL_INTERFACE_KNOWN is still unset but no definition
is available in the file, violating some assertions.
It turns out that for function templates marked inline we would not
respect an 'extern template' imported in general, either; this patch
fixes both of these issues by always treating explicit instantiations as
external, and so marking DECL_INTERFACE_KNOWN eagerly.
For an explicit instantiation declaration we don't want to emit the body
of the function as it must be emitted in a different TU anyway. And for
explicit instantiation definitions we similarly know that it will have
been emitted in the interface TU we streamed it in from, so there's
no need to emit it.
gcc/cp/ChangeLog:
* module.cc (trees_out::core_bools): Always consider explicit
instantiations as external.
gcc/testsuite/ChangeLog:
* g++.dg/modules/extern-tpl-3_a.C: New test.
* g++.dg/modules/extern-tpl-3_b.C: New test.
* g++.dg/modules/extern-tpl-4_a.C: New test.
* g++.dg/modules/extern-tpl-4_b.C: New test.
Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
gcc/cp/module.cc | 11 ++++-
gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C | 11 +++++
gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C | 12 +++++
gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C | 24 ++++++++++
gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C | 46 +++++++++++++++++++
5 files changed, 102 insertions(+), 2 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C
create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C
create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C
create mode 100644 gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 0d9e50bba7f..7440a9015b4 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5660,7 +5660,11 @@ trees_out::core_bools (tree t, bits_out& bits)
we need to import or export any vague-linkage entities on
stream-in. */
bool interface_known = t->decl_common.lang_flag_5;
- if (interface_known && vague_linkage_p (t))
+ if (interface_known && vague_linkage_p (t)
+ /* But explicit instantiations are not vague linkage; we can always
+ rely on there being a definition in another TU. */
+ && !(DECL_LANG_SPECIFIC (t)
+ && DECL_EXPLICIT_INSTANTIATION (t)))
interface_known = false;
WB (interface_known);
}
@@ -5695,7 +5699,10 @@ trees_out::core_bools (tree t, bits_out& bits)
gcc_fallthrough ();
case FUNCTION_DECL:
if (TREE_PUBLIC (t)
- && !vague_linkage_p (t))
+ && (!vague_linkage_p (t)
+ /* Explicit instantiations are always external. */
+ || (DECL_LANG_SPECIFIC (t)
+ && DECL_EXPLICIT_INSTANTIATION (t))))
is_external = true;
break;
}
diff --git a/gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C b/gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C
new file mode 100644
index 00000000000..def3cd1413d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/extern-tpl-3_a.C
@@ -0,0 +1,11 @@
+// { dg-additional-options "-fmodules -Wno-global-module" }
+// { dg-module-cmi M }
+
+module;
+template <typename>
+struct S {
+ S() {}
+};
+export module M;
+extern template class S<int>;
+S<int> s;
diff --git a/gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C b/gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C
new file mode 100644
index 00000000000..5d96937ce02
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/extern-tpl-3_b.C
@@ -0,0 +1,12 @@
+// { dg-additional-options "-fmodules" }
+
+template <typename>
+struct S {
+ S() {}
+};
+
+void foo() { S<double> x;}
+
+import M;
+
+// Lazy loading of extern S<int> at EOF should not ICE
diff --git a/gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C b/gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C
new file mode 100644
index 00000000000..16f1b041307
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/extern-tpl-4_a.C
@@ -0,0 +1,24 @@
+// { dg-additional-options "-fmodules" }
+// { dg-module-cmi M }
+
+export module M;
+
+export template <typename T> inline void a() {}
+extern template void a<int>();
+extern template void a<bool>();
+template void a<char>();
+
+export template <typename T> void b() {}
+extern template void b<int>();
+extern template void b<bool>();
+template void b<char>();
+
+export template <typename T> inline int c = 123;
+extern template int c<int>;
+extern template int c<bool>;
+template int c<char>;
+
+export template <typename T> int d = 123;
+extern template int d<int>;
+extern template int d<bool>;
+template int d<char>;
diff --git a/gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C b/gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C
new file mode 100644
index 00000000000..1dd4afe9f6b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/extern-tpl-4_b.C
@@ -0,0 +1,46 @@
+// { dg-additional-options "-fmodules" }
+
+import M;
+
+int main() {
+ a<int>();
+ a<char>();
+ a<double>();
+
+ b<int>();
+ b<char>();
+ b<double>();
+
+ int x = c<int> + c<char> + c<double>;
+ int y = d<int> + d<char> + d<double>;
+ return x + y;
+}
+
+// 'int': imported explicit instantiation decls should not be emitted here:
+// { dg-final { scan-assembler-not "_ZW1M1aIiEvv:" } }
+// { dg-final { scan-assembler-not "_ZW1M1bIiEvv:" } }
+// { dg-final { scan-assembler-not "_ZW1M1cIiE:" } }
+// { dg-final { scan-assembler-not "_ZW1M1dIiE:" } }
+
+// 'char': explicit instantiation definitions don't need to be emitted here:
+// { dg-final { scan-assembler-not "_ZW1M1aIcEvv:" } }
+// { dg-final { scan-assembler-not "_ZW1M1bIcEvv:" } }
+// { dg-final { scan-assembler-not "_ZW1M1cIcE:" } }
+// { dg-final { scan-assembler-not "_ZW1M1dIcE:" } }
+
+// 'double': these are not explicitly instantiated and should be emitted here:
+// { dg-final { scan-assembler "_ZW1M1aIdEvv:" } }
+// { dg-final { scan-assembler "_ZW1M1bIdEvv:" } }
+// { dg-final { scan-assembler "_ZW1M1cIdE:" } }
+// { dg-final { scan-assembler "_ZW1M1dIdE:" } }
+
+template void a<bool>();
+template void b<bool>();
+template int c<bool>;
+template int d<bool>;
+
+// 'bool': instantiated in this file, and so must be emitted here:
+// { dg-final { scan-assembler "_ZW1M1aIbEvv:" } }
+// { dg-final { scan-assembler "_ZW1M1bIbEvv:" } }
+// { dg-final { scan-assembler "_ZW1M1cIbE:" } }
+// { dg-final { scan-assembler "_ZW1M1dIbE:" } }
@@ -2480,6 +2480,9 @@ vague_linkage_p (tree decl)
/* Unfortunately, import_export_decl has not always been called
before the function is processed, so we cannot simply check
DECL_COMDAT. */
+ if (DECL_LANG_SPECIFIC (decl)
+ && DECL_EXPLICIT_INSTANTIATION (decl))
+ return false;
if (DECL_COMDAT (decl)
|| (TREE_CODE (decl) == FUNCTION_DECL
&& DECL_DECLARED_INLINE_P (decl)
new file mode 100644
@@ -0,0 +1,11 @@
+// { dg-additional-options "-fmodules -Wno-global-module" }
+// { dg-module-cmi M }
+
+module;
+template <typename>
+struct S {
+ S() {}
+};
+export module M;
+extern template class S<int>;
+S<int> s;
new file mode 100644
@@ -0,0 +1,12 @@
+// { dg-additional-options "-fmodules" }
+
+template <typename>
+struct S {
+ S() {}
+};
+
+void foo() { S<double> x;}
+
+import M;
+
+// Lazy loading of extern S<int> at EOF should not ICE
new file mode 100644
@@ -0,0 +1,24 @@
+// { dg-additional-options "-fmodules" }
+// { dg-module-cmi M }
+
+export module M;
+
+export template <typename T> inline void a() {}
+extern template void a<int>();
+extern template void a<bool>();
+template void a<char>();
+
+export template <typename T> void b() {}
+extern template void b<int>();
+extern template void b<bool>();
+template void b<char>();
+
+export template <typename T> inline int c = 123;
+extern template int c<int>;
+extern template int c<bool>;
+template int c<char>;
+
+export template <typename T> int d = 123;
+extern template int d<int>;
+extern template int d<bool>;
+template int d<char>;
new file mode 100644
@@ -0,0 +1,46 @@
+// { dg-additional-options "-fmodules" }
+
+import M;
+
+int main() {
+ a<int>();
+ a<char>();
+ a<double>();
+
+ b<int>();
+ b<char>();
+ b<double>();
+
+ int x = c<int> + c<char> + c<double>;
+ int y = d<int> + d<char> + d<double>;
+ return x + y;
+}
+
+// 'int': imported explicit instantiation decls should not be emitted here:
+// { dg-final { scan-assembler-not "_ZW1M1aIiEvv:" } }
+// { dg-final { scan-assembler-not "_ZW1M1bIiEvv:" } }
+// { dg-final { scan-assembler-not "_ZW1M1cIiE:" } }
+// { dg-final { scan-assembler-not "_ZW1M1dIiE:" } }
+
+// 'char': explicit instantiation definitions don't need to be emitted here:
+// { dg-final { scan-assembler-not "_ZW1M1aIcEvv:" } }
+// { dg-final { scan-assembler-not "_ZW1M1bIcEvv:" } }
+// { dg-final { scan-assembler-not "_ZW1M1cIcE:" } }
+// { dg-final { scan-assembler-not "_ZW1M1dIcE:" } }
+
+// 'double': these are not explicitly instantiated and should be emitted here:
+// { dg-final { scan-assembler "_ZW1M1aIdEvv:" } }
+// { dg-final { scan-assembler "_ZW1M1bIdEvv:" } }
+// { dg-final { scan-assembler "_ZW1M1cIdE:" } }
+// { dg-final { scan-assembler "_ZW1M1dIdE:" } }
+
+template void a<bool>();
+template void b<bool>();
+template int c<bool>;
+template int d<bool>;
+
+// 'bool': instantiated in this file, and so must be emitted here:
+// { dg-final { scan-assembler "_ZW1M1aIbEvv:" } }
+// { dg-final { scan-assembler "_ZW1M1bIbEvv:" } }
+// { dg-final { scan-assembler "_ZW1M1cIbE:" } }
+// { dg-final { scan-assembler "_ZW1M1dIbE:" } }