Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a7f0683
Add LINK_TIME_REGISTRATION prototype: self-registering module rollcal…
claude Jun 11, 2026
2f6bb73
Make harvested headers deterministic across runs
claude Jun 11, 2026
d3e6a3f
Add link-time registration design note; ignore build_* dirs
claude Jun 11, 2026
c46111b
Fix duplicate static-member definitions in registration TUs; add meas…
claude Jun 11, 2026
e21b8d0
Honour explicit -DWITH_HEPMC=OFF / -DWITH_YODA=OFF with ColliderBit i…
claude Jun 12, 2026
567bd30
Migrate ColliderBit to link-time registration
claude Jun 12, 2026
d3dd8ef
Make in-core macro header self-contained w.r.t. backend ini functions
claude Jun 12, 2026
5f3c35d
Document ColliderBit migration findings (measurements to follow)
claude Jun 12, 2026
e16a97c
Add ColliderBit link-time registration measurements and evidence logs
claude Jun 12, 2026
d20fb32
Move backend functor registration to link time under LINK_TIME_REGIST…
claude Jun 12, 2026
e4664ee
Make backend harvester outputs deterministic and idempotent
claude Jun 12, 2026
7727bbe
Add Backends link-time registration measurements and evidence logs
claude Jun 12, 2026
985275f
Migrate all remaining Bits to link-time registration
claude Jun 12, 2026
2cd4734
Document all-Bits link-time registration validation and measurements
claude Jun 12, 2026
b2777e1
Add final all-Bits measurement: small-Bit rollcall edit 25m41s -> 49s
claude Jun 12, 2026
f6fae5e
Drop prototype / ExampleBit_A-only framing from comments and notes
claude Jun 15, 2026
c67d8c9
Merge branch 'master' into claude/nice-bell-mvcd6j
ChrisJChang Jun 22, 2026
71f32f5
Remove unneccessary doc files
ChrisJChang Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ results/*
# Executables and build files
/gambit
build/*
build_*/
*_standalone

# Generated cmake files
Expand Down
9 changes: 9 additions & 0 deletions BUILD_OPTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ For a more complete list of cmake variables, take a look in the file `CMakeCache
-DBits="CosmoBit;DarkBit" # typical cosmology project


# Register Bits' module functors and the backend functors with the
# Core at link time instead of compiling their rollcall headers into
# the Core: LINK_TIME_REGISTRATION (On|Off, default Off)
# Editing a Bit's rollcall header then recompiles only that Bit's
# objects plus a relink, rather than the Core's largest translation
# units. See doc/link_time_registration.md.
-DLINK_TIME_REGISTRATION=On


# List the FlexibleSUSY models to build: BUILD_FS_MODELS
# The names of the available FlexibleSUSY models correspond to
# the subdirectories in
Expand Down
6 changes: 6 additions & 0 deletions Backends/include/gambit/Backends/backend_info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ namespace Gambit
/// Given a backend and a true version (with periods), return the safe version
str safe_version_from_version (str, str) const;

/// Check whether a backend safe version (no periods) has been registered yet
bool has_safe_version (const str&, const str&) const;

/// Check whether a backend true version (with periods) has been registered yet
bool has_version (const str&, const str&) const;

/// Link a backend's version and safe version
void link_versions(str, str, str);

Expand Down
8 changes: 8 additions & 0 deletions Backends/include/gambit/Backends/ini_functions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ namespace Gambit
/// Set the classloading requirements of a given functor.
int set_classload_requirements(module_functor_common&, str, str, str);

/// Apply any deferred classloading requirements whose backend version
/// information has become available.
void process_deferred_classload_requirements();

/// Raise an error for any classloading requirement that is still unfulfilled
/// once static initialisation is over.
void check_deferred_classload_requirements();

namespace Backends
{

Expand Down
56 changes: 56 additions & 0 deletions Backends/registration/Backends_registration.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// GAMBIT: Global and Modular BSM Inference Tool
// *********************************************
/// \file
///
/// Link-time registration translation unit for
/// the GAMBIT backends.
///
/// This file expands the generated
/// backend_rollcall.hpp (i.e. every frontend
/// header) in the in-core macro context, exactly
/// as gambit.hpp used to do inside the Core, but
/// from within a translation unit owned by the
/// Backends directory. All backend functor
/// definitions, BackendIniBit module functors,
/// classloading bookkeeping and registration
/// calls produced by the backend macros are
/// therefore compiled into this object file, and
/// the registrations happen during static
/// initialisation, before main().
///
/// Unlike the per-Bit registration TUs, no
/// harvester change is involved: gambit.hpp
/// simply skips its #include of
/// backend_rollcall.hpp when the global
/// LINK_TIME_REGISTRATION compile definition is
/// set, and this TU includes it instead.
///
/// This file is only compiled into the main
/// gambit executable, and only when the CMake
/// option LINK_TIME_REGISTRATION is ON. It must
/// NOT be compiled into the Backends object
/// library: standalone executables expand
/// backend_rollcall.hpp from their own main
/// translation unit via standalone_module.hpp
/// (with STANDALONE defined), and would suffer
/// duplicate-symbol errors.
///
/// *********************************************
///
/// Authors (add name and date if you modify):
///
/// \author The GAMBIT Collaboration
/// \date 2026 Jun
///
/// *********************************************

#ifdef LINK_TIME_REGISTRATION

/* The static members defined by static_members.hpp (pulled in via the in-core
macros) are provided by the main gambit translation unit; defining them here
too would break the link with duplicate definitions. */
#define GAMBIT_NO_STATIC_MEMBER_DEFINITIONS 1

#include "gambit/Backends/backend_rollcall.hpp"

#endif
22 changes: 22 additions & 0 deletions Backends/src/backend_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include "gambit/cmake/cmake_variables.hpp"
#include "gambit/Backends/backend_info.hpp"
#include "gambit/Backends/ini_functions.hpp"
#include "gambit/Utils/util_functions.hpp"
#include "gambit/Utils/python_interpreter.hpp"
#include "gambit/Logs/logger.hpp"
Expand Down Expand Up @@ -226,11 +227,32 @@ namespace Gambit
return safe_version_map.at(be).second.at(v);
}

/// Check whether a backend safe version (no periods) has been registered yet
bool Backends::backend_info::has_safe_version (const str& be, const str& sv) const
{
auto it = safe_version_map.find(be);
return it != safe_version_map.end() and it->second.first.find(sv) != it->second.first.end();
}

/// Check whether a backend true version (with periods) has been registered yet
bool Backends::backend_info::has_version (const str& be, const str& v) const
{
auto it = safe_version_map.find(be);
return it != safe_version_map.end() and it->second.second.find(v) != it->second.second.end();
}

/// Link a backend's version and safe version
void Backends::backend_info::link_versions(str be, str v, str sv)
{
safe_version_map[be].first[sv] = v;
safe_version_map[be].second[v] = sv;
// A new backend version is available, so try to fulfil any classloading
// requirements that were deferred because this backend had not yet been
// registered. (With link-time registration, the static-initialisation
// order of module and backend registration translation units is
// unspecified, so a module's NEEDS_CLASSES_FROM may run before the
// backend it refers to has registered its versions.)
process_deferred_classload_requirements();
}

/// Override a backend's config file location
Expand Down
102 changes: 92 additions & 10 deletions Backends/src/ini_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,26 +372,108 @@ namespace Gambit
}


/// A classloading requirement that could not be applied immediately, because
/// the backend it refers to had not yet registered its versions. This happens
/// with link-time registration, where the static-initialisation order of the
/// module and backend registration translation units is unspecified.
struct deferred_classload_request
{
module_functor_common* f;
str be;
str verstr;
str default_ver;
};

/// Pending classloading requirements (function-local static so that it is
/// safe to access during static initialisation).
std::vector<deferred_classload_request>& deferred_classload_requests()
{
static std::vector<deferred_classload_request> requests;
return requests;
}

/// Try to apply a classloading requirement to a functor. Returns false
/// (without side effects on the functor) if the backend has not yet
/// registered all the version information that the requirement refers to.
bool try_set_classload_requirements(module_functor_common& f, const str& be, const str& verstr, const str& default_ver)
{
// Split up the passed version string into individual versions
std::vector<str> versions = Utils::delimiterSplit(verstr, ",");
// First make sure every needed version is known, so that the requirement
// is applied either completely or not at all.
for (const str& v : versions)
{
if (v == "default")
{
if (not Backends::backendInfo().has_safe_version(be, default_ver)) return false;
}
else
{
if (not Backends::backendInfo().has_version(be, v)) return false;
}
}
// Add each version individually as required for classloading
for (str v : versions)
{
// Retrieve the version corresponding to the default if needed
if (v == "default") v = Backends::backendInfo().version_from_safe_version(be, default_ver);
// Retrieve the safe version corresponding to this version
str sv = Backends::backendInfo().safe_version_from_version(be, v);
// Set the requirement in the functor
f.setRequiredClassloader(be,v,sv);
}
return true;
}

/// Set the classloading requirements of a given functor.
int set_classload_requirements(module_functor_common& f, str be, str verstr, str default_ver)
{
try
{
// Split up the passed version string into individual versions
std::vector<str> versions = Utils::delimiterSplit(verstr, ",");
// Add each version individually as required for classloading
for (auto it = versions.begin() ; it != versions.end(); ++it)
// If the backend's versions are not all registered yet, record the
// requirement passively; backend_info::link_versions retries the queue
// every time a backend registers a new version.
if (not try_set_classload_requirements(f, be, verstr, default_ver))
{
// Retrieve the version corresponding to the default if needed
if (*it == "default") *it = Backends::backendInfo().version_from_safe_version(be, default_ver);
// Retrieve the safe version corresponding to this version
str sv = Backends::backendInfo().safe_version_from_version(be, *it);
// Set the requirement in the functor
f.setRequiredClassloader(be,*it,sv);
deferred_classload_requests().push_back({&f, be, verstr, default_ver});
}
}
catch (std::exception& e) { ini_catch(e); }
return 0;
}

/// Apply any deferred classloading requirements whose backend version
/// information has become available. Called from
/// backend_info::link_versions whenever a backend registers a version.
void process_deferred_classload_requirements()
{
auto& requests = deferred_classload_requests();
for (auto it = requests.begin(); it != requests.end(); )
{
if (try_set_classload_requirements(*(it->f), it->be, it->verstr, it->default_ver))
{
it = requests.erase(it);
}
else ++it;
}
}

/// Raise an error for any classloading requirement that is still unfulfilled
/// once all backends have had the chance to register (i.e. once static
/// initialisation is over). Called from the Core before functor activation.
void check_deferred_classload_requirements()
{
for (const auto& request : deferred_classload_requests())
{
std::ostringstream msg;
msg << "The classloading requirement NEEDS_CLASSES_FROM(" << request.be
<< ", " << request.verstr << ") of module function "
<< request.f->origin() << "::" << request.f->name()
<< " refers to a backend version that never registered itself."
<< "\nEither the backend \"" << request.be << "\" is not known to"
<< "\nGAMBIT at all, or the requested versions do not exist.";
backend_error().raise(LOCAL_INFO, msg.str());
}
}

}
37 changes: 37 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,43 @@ include(cmake/externals.cmake)

string (REPLACE ";" "," itch_with_commas "${itch}")

# Link-time (self-registering) module registration. Bits compile their own
# in-core rollcall macro expansions into a per-Bit registration translation
# unit (<Bit>/registration/<Bit>_registration.cpp) that is linked only into the gambit
# executable, instead of having their rollcall headers #included into the Core via the
# generated module_rollcall.hpp. Editing a Bit's rollcall header then
# recompiles only that Bit's objects (plus a relink), not the Core.
option(LINK_TIME_REGISTRATION "Register Bits' module functors and the backend functors with the Core at link time instead of compiling their rollcall headers into the Core" OFF)
set(LINK_TIME_REGISTRATION_BITS "")
set(LINK_TIME_REGISTRATION_COMPONENTS "")
if(LINK_TIME_REGISTRATION)
# Bits using link-time registration. A Bit can only be listed here if it has
# a registration translation unit at
# <Bit>/registration/<Bit>_registration.cpp.
foreach(bit ColliderBit CosmoBit DarkBit DecayBit ExampleBit_A ExampleBit_B FlavBit NeutrinoBit ObjectivesBit PrecisionBit SpecBit)
if(";${GAMBIT_BITS};" MATCHES ";${bit};")
list(APPEND LINK_TIME_REGISTRATION_BITS ${bit})
endif()
endforeach()
set(LINK_TIME_REGISTRATION_COMPONENTS ${LINK_TIME_REGISTRATION_BITS})
# The backends follow the same pattern, but are not a Bit: the generated
# backend_rollcall.hpp (all frontend headers) is expanded in a single
# registration TU instead of being #included into the Core by gambit.hpp.
# The harvesters are unaffected; gambit.hpp skips the include via the
# LINK_TIME_REGISTRATION compile definition.
if(EXISTS "${PROJECT_SOURCE_DIR}/Backends/")
list(APPEND LINK_TIME_REGISTRATION_COMPONENTS Backends)
endif()
endif()
if(LINK_TIME_REGISTRATION_COMPONENTS)
message("${BoldYellow}-- Link-time registration enabled for: ${LINK_TIME_REGISTRATION_COMPONENTS}${ColourReset}")
add_definitions(-DLINK_TIME_REGISTRATION=1)
endif()
if(LINK_TIME_REGISTRATION_BITS)
string(REPLACE ";" "," ltr_bits_with_commas "${LINK_TIME_REGISTRATION_BITS}")
set(MODULE_HARVESTER_EXTRA_ARGS -r ${ltr_bits_with_commas})
endif()

if(EXISTS "${PROJECT_SOURCE_DIR}/Elements/")
add_gambit_custom(module_harvest modules_harvested MODULE_HARVESTER MODULE_HARVESTER_FILES ${itch_with_commas})
endif()
Expand Down
Loading