On Wed, Jun 8, 2022 at 7:23 PM Nathan Sidwell via SG15 <sg15@lists.isocpp.org> 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@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/sg15