mirror of
https://github.com/logos-co/logos-protocol.git
synced 2026-08-27 12:01:15 +00:00
PR-1+PR-2 of the shared-runtime migration, squashed: they were raised
separately and the second replaced the first's mechanism, so the split was
history rather than review value.
WHY. The runtime types that must exist EXACTLY ONCE per process (TokenManager,
LogosAPIClient, the per-identity StoreRegistry) are moving from "absorbed into
liblogos_core by whole-archive and re-exported through a generated .def" to
"owned by the shared library that defines them". Every image that links a static
archive gets its own copy of every function-local static inside it, so the host
writes a capability token into one store and another in-process image reads an
empty one -- with no build diagnostic.
Three things, and the order they were discovered in is the order they matter:
1. THE CMAKE PACKAGE. logos_protocol_shared was built and installed but
deliberately kept OUT of the export set: it existed only for FFI callers that
dlopen the lp_* C ABI, and those never link it. In-process C++ consumers do,
and a consumer cannot link what find_package() does not hand it. Now exported
as logos-protocol::logos_protocol_shared, with the INSTALL_INTERFACE include
dirs the static target already had, and with ARCHIVE DESTINATION -- on Windows
a shared library's import library (.dll.a) is the ARCHIVE artifact, so
omitting it installs no import library at all and the failure is invisible on
ELF and Mach-O, which have none.
2. THE EXPORT TABLE IS GENERATED, NOT HAND-MARKED. The first attempt annotated
the classes with __declspec(dllexport). That exported 116 symbols and the Qt
host runtime STILL failed to link against it, with ELEVEN undefined
references across five classes -- LogosProviderObject and its vtable,
ModuleProxy, ModuleHandshakeProxy, LogosTransportFactory -- plus free
functions such as logos::qvariantToNlohmann. A curated list is correct only
until the next consumer touches a symbol nobody marked, and the failure lands
in a downstream repo far from the cause.
cmake/gen-shared-exports.sh is adapted from logos-liblogos, which generated
the same table one layer up. The mechanism is unchanged because the reasons
for it are unchanged; this moves it down to the library that owns the
symbols. It cannot be shared as a file: logos-liblogos depends on
logos-protocol, not the other way round.
logos_shared_api.h therefore resolves its "building the shared library"
branch to NOTHING on Windows, so the .def and the annotations never compete.
The macro keeps its import half, which is what stops a consumer pulling the
archive member that would redefine the symbol.
30 exports on master -> 116 hand-marked -> 360 generated.
3. VTABLES AND TYPEINFO ARE CARVED OUT OF THE COMDAT FILTER. The last undefined
symbol was the vtable for LogosProviderObject. PE HAS NO WEAK SYMBOLS --
COMDAT is the mechanism for weak and inline linkage -- so GCC emits a vtable
into .rdata$_ZTV... even when the class has a key function and the vtable is a
single strong definition. The section name cannot tell "one definition nobody
duplicates" from "every TU emits its own", so the filter dropped it. The
filter's reasoning does not apply to vtables: a consumer of a class WITH a key
function emits a .refptr and needs ours; a class WITHOUT one emits its own
copy and never references ours, so exporting is inert. This never mattered
while liblogos_core absorbed both archives -- definition and consumer landed
in one image and the reference never crossed a boundary.
WHY PROTOCOL NEEDS A .def WHEN THE QT HOST DOES NOT. The shared qt-host DLL
exports 2799 symbols with no .def at all, because it carries no dllexport marks
and GNU ld auto-exports everything. Protocol cannot rely on that: LP_API's
dllexport on the lp_* C ABI disables auto-export for the whole target. ANY single
dllexport turns the automatic path off -- which is also why CMake's
WINDOWS_EXPORT_ALL_SYMBOLS was measured as completely inert here.
TWO MACROS, NOT ONE. LOGOS_QT_HOST_API is added for logos-plugin-qt's LogosAPI,
which lives in a different library. While building the Qt host shared library
LogosAPI must NOT be dllimport while TokenManager must be, and one macro cannot
say both in the same translation unit. Off Windows the distinction is moot --
both resolve to default visibility -- which is exactly why getting it wrong would
go unnoticed until a Windows build.
Also corrects this file's own premise, the origin of the false claim that "ELF
and Mach-O give this for free. Both formats interpose symbols across the whole
process image set." True of ELF, false of Mach-O, whose two-level namespace gives
no interposition -- measured in logos-basecamp, where one reference to
LogosAPI::forIdentity dragged logos_api.cpp.o into the executable and produced 31
refused calls against a baseline of 0.
VERIFIED.
aarch64-darwin static archive symbol tables IDENTICAL (14257 lines), exactly
ONE byte differing in 5.4MB -- 351 -> 352 in __.SYMDEF's ar
header, build metadata, not content
logos-protocolTargets.cmake names both targets
checks.tests PASS, .#default PASS
x86_64-linux checks.tests PASS
x86_64-mingw export table 30 -> 360, all 30 lp_* preserved, 0 removed
import library liblogos_protocol.dll.a now installed
logos-plugin-qt#22 links against it, and its PE layering is
correct: defines LogosAPI 25, defines TokenManager 0 and
LogosAPIClient 0, imports from liblogos_protocol.dll
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>