core: move BRIDGE_REGISTER_MODEL/BRIDGE_REGISTER_ACTION's registrar out of the header - #837
Merged
Merged
Conversation
Yaraslaut
force-pushed
the
feature/791-registrar-relocation
branch
2 times, most recently
from
September 26, 2026 19:57
12f630c to
39858e7
Compare
…ut of the header BRIDGE_REGISTER_MODEL/BRIDGE_REGISTER_ACTION's registrar initialiser is an ordinary function call written where the macro is expanded, so its body -- and everything it drags in (the action's glaze codecs, the dispatcher's runner closure, forms::schemaJson<A>'s schema generator) -- is instantiated in every translation unit that includes a model header carrying it. Measured on examples/kanban/include/kanban/models/board_model.hpp (morph#791): 26.93 CPU-seconds of -O3 codegen per including TU, for codecs the overwhelming majority of those TUs never call. BRIDGE_DECLARE_MODEL/BRIDGE_DECLARE_ACTION decompose the existing macros into a header-safe half (the ModelTraits<M>/ActionTraits<A> specialisation alone, free to repeat per TU) and BRIDGE_REGISTER_MODEL_SOURCE/ BRIDGE_REGISTER_ACTION_SOURCE carry the registration itself, called once in the .cpp that should own it. The combined BRIDGE_REGISTER_MODEL/ BRIDGE_REGISTER_ACTION macros are unchanged and remain the right choice when the split isn't worth it. Splitting declaration from registration introduces a mistake that couldn't previously happen: a model declared but never registered anywhere. Closed with a link-time canary -- BRIDGE_DECLARE_MODEL/BRIDGE_DECLARE_ACTION call a function template behind `extern template`, and only the SOURCE macros explicitly instantiate it -- so a missing registration fails to *link*, instead of compiling clean and surfacing only as a runtime "unknown model type" far from the cause. Proven both ways by tests/compile_checks/declare_only_no_source_link.cpp/ declare_only_source_provided.cpp via a new try_compile() pair in tests/CMakeLists.txt. Retrofits examples/kanban's board_model.hpp/.cpp -- the exact header morph#791 measured -- as the proof this reaches the measured cost. Re-measured (AppleClang 17, not isolated from concurrent system load so reported as a lower bound): 20.81 CPU-s before, 7.81 CPU-s after, a 13.00 CPU-s reduction per translation unit; the compiled stub's object file shrinks from 3,331,704 to 3,232 bytes (7155 to 24 symbols), with zero glaze symbols left in the post-fix object. docs/spec/core/registry.md gains "Moving a registrar out of the header", documenting the mechanism, the canary, and its one real gap (a `dlopen`ed plugin module, which must keep using the combined macros). Closes #791 Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fEUahMFF32wQLiWjbsfkc
…_compile A try_compile() scratch project cannot link core-cpp's compiled modules (established just above, for the MORPH_CLIENT_ONLY guard, when morph moved onto core-cpp v0.5.0). BRIDGE_REGISTER_ACTION_SOURCE routes through registerActionExecutorOnce's definition in bridge.hpp, which pulls in TimeoutScheduler and, through it, core::net -- so the "must link" probe needs the same real, linked-executable treatment as that guard's own positive probes, via morph_guard_probe_deps. The "must fail to link" probe stays a try_compile(): it never reaches Bridge's registration machinery, so it needs nothing beyond MORPH_VETTED_HMAC_GUARD_INCLUDE_DIRS. Also drops a stale try_run()-based client_only_runtime_throw check that had survived, unchanged, as merge-conflict context from before core-cpp v0.5.0 -- duplicating the add_executable()-based check already added above.
Yaraslaut
force-pushed
the
feature/791-registrar-relocation
branch
from
September 27, 2026 10:06
61d726c to
95d2b49
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
BRIDGE_REGISTER_MODEL/BRIDGE_REGISTER_ACTION's registrar initialiser is an ordinary function call written where the macro is expanded, so its body — and everything it drags in (the action's glaze codecs, the dispatcher's runner closure,forms::schemaJson<A>'s schema generator) — is instantiated in every translation unit that includes a model header carrying it. A prior measurement onexamples/kanban/include/kanban/models/board_model.hppfound 26.93 CPU-seconds of-O3codegen per including TU, for codecs the overwhelming majority of those TUs never call.BRIDGE_DECLARE_MODEL/BRIDGE_DECLARE_ACTIONdecompose the existing macros into a header-safe half (theModelTraits<M>/ActionTraits<A>specialisation alone, free to repeat per TU) andBRIDGE_REGISTER_MODEL_SOURCE/BRIDGE_REGISTER_ACTION_SOURCEcarry the registration itself, called once in the.cppthat should own it. The combinedBRIDGE_REGISTER_MODEL/BRIDGE_REGISTER_ACTIONare unchanged and remain the right choice when the split isn't worth it.BRIDGE_DECLARE_MODEL/BRIDGE_DECLARE_ACTIONcall a function template behindextern template, and only theSOURCEmacros explicitly instantiate it — so a missing registration fails to link, instead of compiling clean and surfacing only as a runtime"unknown model type"far from the cause.examples/kanban'sboard_model.hpp/.cpp— the exact header the original measurement used — as proof this reaches the measured cost. Re-measured (AppleClang 17, not isolated from concurrent system load on this run, so reported as a lower bound): 20.81 CPU-s before, 7.81 CPU-s after, a 13.00 CPU-s reduction per translation unit; the compiled stub's object file shrinks from 3,331,704 to 3,232 bytes (7155 to 24 symbols), with zero glaze symbols left in the post-fix object.docs/spec/core/registry.mdgains "Moving a registrar out of the header", documenting the mechanism, the canary, and its real gaps (adlopened plugin module must keep using the combined macros; the static-library archive-drop interaction is stated as inferred, not measured).Closes #791
Test plan
morph_tests, 1602 test cases / 23513 assertions) green, both before and after a rebase onto currentmaster.examples/kanbanladder suite (ladder_kanban_tests, 172 test cases / 1192 assertions) green.tests/compile_checks/declare_only_no_source_link.cpp+declare_only_source_provided.cpp, wired through a newtry_compile()pair intests/CMakeLists.txt, prove the canary both directions: a declared-but-unregistered model/action fails to link, and the same program links cleanly once theSOURCEmacros are added.examples/kanban'sladder_kanban_lib,ladder_kanban_server, andladder_kanban_headlessall link successfully against the retrofittedboard_model.hpp/.cpp.board_model.hpp(methodology matching the original issue:-fsyntax-onlyand-O3 -c, best of two, on a stub TU that only includes the header), corroborated by object-file size and symbol-count diffs.🤖 Generated with Claude Code
https://claude.ai/code/session_018fEUahMFF32wQLiWjbsfkc