C++ Logo

sg15

Advanced search

Re: unimported implementation partitions

From: Mathias Stearn <redbeard0531_at_[hidden]>
Date: Wed, 8 Jun 2022 20:59:59 +0200
On Wed, Jun 8, 2022 at 7:23 PM Nathan Sidwell via SG15 <
sg15_at_[hidden]> wrote:

> Q1) Is there a use case for implementation partitions (a non-interface
> partition) that are not imported in any module unit? How would that
> differ from a regular implementation partition?
>

I don't know if this really qualifies as a "use case", but I think a lot of
people will misunderstand implementation partitions and make all
implementation units partitions with unique names, except possibly the
implementation unit for things declared in the primary interface unit.
There are two things that aren't obvious: 1) there is an asymmetry between
interface and implementation units without a partition name, you can only
have 1 of the former, and I think people will assume that you can also only
have 1 "primary implementation unit" (I know it took a while to get that
notion out of my head...) 2) implementation partitions are a bit weird and
not something you often need. They can almost always be replaced with an
interface partition that doesn't export anything. And the only reason for
the “almost” there is because we artificially require the PMI to
transitively reexport all interface units even if they don’t export
anything.



> We require all interface partitions be [transitively] imported in the
> primary interface. I don't think we require implementation partitions
> be imported at least once in a program.
>
> Q2) Is there a use case for a program to include a module interface that
> is not imported in any other TU (and has no implementation units)?


Yes. I can imagine lumping many modules into a single library. My main
executable may import all of them somewhere, however I may want to have
separate unit test binaries for modules A and B that only import their own
module, but link against a lib that has both. And for the sake of argument,
let’s assume that both modules have some self-registering objects in
dynamic init, and it is important that they both run in both unit tests.


The reason I ask is there an ABI issue with the global initializer
> function needed for p1874. I'm going to describe it in ELF terms, but I
> imagine the same choices appear in other ABIs.
>
> 1874 is solved by having each module primary interface, and all
> partitions, emit an idempotent initialization function that (a) calls
> the init fn of all imports and then (b) performs all dynamic inits of
> namespace scope.


That sounds like it will result in roughly one function call per import
statement in the program prior to main. How ABI resilient does this need to
be? Could the PMI collect up all imports from all of its interface units
and imported partitions to remove duplicates, or do you need to support
replacing a single interface partition without recompiling the PMI? Is a TU
allowed to omit calling the initializer for one of its imports A if it can
see that another import B also has an interface dependency on A, or do you
need to be prepared for B to be replaced with a version that doesn’t import
A transitively? If some leaf TU doesn’t have any dynamic init and says so
in its BMI, do importers still need to call its init function in case it is
replaced? If not can this be transitively done for any TU where all
interface dependencies have no dynamic init?

Also, is there any way that this could instead be written in some metadata
tables showing the direct dependencies and resolved by the linker into a
single ordered list of either flat per-TU or per-object initializers for
each DSO? That seems a bit better to me than trying to do it
pessimistically via function calls.

>

> In a non-module world a function that performs #b would then arrange to
> be called at startup via .init or similar. In a module-world, we do not
> (need to) do this, as we know it'll be called somewhere by an import.
>
> However, Q1 raises the possibility that an implementation partition may
> not be imported anywhere, so its global initializer fn is never called
> from another global init. We therefore have to arrange for it to be
> called from .init as a regular initializer function. A small
> pessimization.


That optimization would only help for implementation partitions that have
their own dynamic init. That seems likely to be enough of an edge case to
not be worth microoptimizing for, even if it were valid to do so.

>
>
> Q2 raises the same question wrt primary interfaces. If they might never
> be imported, again, their initializer fn would never be called by that
> mechanism. We have to emit it via .init.
>
> nathan
>
> --
> Nathan Sidwell
> _______________________________________________
> SG15 mailing list
> SG15_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg15
>

Received on 2022-06-08 19:00:11