Add build-wide default optimization level (size) option#2633
Merged
Conversation
Lets embedded users make `size` the default optimization level for the whole
build instead of passing glz::opts_size{} at every call site. A single forgotten
default-options call (e.g. glz::write(obj, buf) or a transitive glz::format_error)
can no longer silently relink the 40KB integer, ~16KB float pow-10, or per-struct
hash tables.
The selection is value-based and reuses the existing optimization_level machinery
rather than adding a parallel #ifdef path:
- optimization_level.hpp: add exported `glz::default_optimization_level`, fed by
GLZ_DEFAULT_OPTIMIZATION_SIZE (boolean) / GLZ_DEFAULT_OPTIMIZATION_LEVEL (level).
- opts.hpp: route the check_optimization_level and check_linear_search fallbacks
through the constant, so integers, floats, and hash-table elimination flip
together. Explicit per-call options still win (optimization_level::normal keeps
the fast tables). Behavior is unchanged in the default (normal) build.
- CMakeLists.txt: glaze_DEFAULT_OPTIMIZATION_SIZE option injects the macro as an
INTERFACE compile definition (build-wide, ODR-consistent).
Also close two internal leaks so size users actually get the guarantee:
- validate.hpp: glz::format_error formats index/line/column with opts_size{} (these
are size_t only). Error formatting is never a throughput path.
- jsonb/write.hpp: BEVE float writes preserve the caller's optimization level instead
of hardcoding normal opts, dropping the float pow-10 tables under size mode.
Because default_optimization_level is a value consumed via if constexpr (never a
preprocessor branch in serialization code), the design composes with future C++20
modules: the default is baked once when the module is built and consumed by name.
tests/size_optimization_test verifies the wiring in both modes (static_assert) and
asserts via nm that a size-default binary does not link the 40KB digit_quads table,
so a future hardcoded-normal regression fails CI.
Fully qualify the member type as glz::optimization_level in the test's escape-hatch opts structs, matching glz::opts_size, so GCC does not flag a member named the same as its unqualified type under -Werror. Also store the error_ctx once in the symbols probe instead of re-parsing.
annihilatorq
pushed a commit
to annihilatorq/glaze
that referenced
this pull request
Jun 16, 2026
…#2633) * Add build-wide default optimization level (size) option Lets embedded users make `size` the default optimization level for the whole build instead of passing glz::opts_size{} at every call site. A single forgotten default-options call (e.g. glz::write(obj, buf) or a transitive glz::format_error) can no longer silently relink the 40KB integer, ~16KB float pow-10, or per-struct hash tables. The selection is value-based and reuses the existing optimization_level machinery rather than adding a parallel #ifdef path: - optimization_level.hpp: add exported `glz::default_optimization_level`, fed by GLZ_DEFAULT_OPTIMIZATION_SIZE (boolean) / GLZ_DEFAULT_OPTIMIZATION_LEVEL (level). - opts.hpp: route the check_optimization_level and check_linear_search fallbacks through the constant, so integers, floats, and hash-table elimination flip together. Explicit per-call options still win (optimization_level::normal keeps the fast tables). Behavior is unchanged in the default (normal) build. - CMakeLists.txt: glaze_DEFAULT_OPTIMIZATION_SIZE option injects the macro as an INTERFACE compile definition (build-wide, ODR-consistent). Also close two internal leaks so size users actually get the guarantee: - validate.hpp: glz::format_error formats index/line/column with opts_size{} (these are size_t only). Error formatting is never a throughput path. - jsonb/write.hpp: BEVE float writes preserve the caller's optimization level instead of hardcoding normal opts, dropping the float pow-10 tables under size mode. Because default_optimization_level is a value consumed via if constexpr (never a preprocessor branch in serialization code), the design composes with future C++20 modules: the default is baked once when the module is built and consumed by name. tests/size_optimization_test verifies the wiring in both modes (static_assert) and asserts via nm that a size-default binary does not link the 40KB digit_quads table, so a future hardcoded-normal regression fails CI. * Fix GCC -Wchanges-meaning in size_optimization_test Fully qualify the member type as glz::optimization_level in the test's escape-hatch opts structs, matching glz::opts_size, so GCC does not flag a member named the same as its unqualified type under -Werror. Also store the error_ctx once in the symbols probe instead of re-parsing.
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
Lets embedded users make
sizethe default optimization level for the whole build, instead of remembering to passglz::opts_size{}at every call site. A single forgotten default-options call (e.g.glz::write(obj, buf), or a transitiveglz::format_error) can no longer silently relink the 40 KB integerdigit_quadstable, the ~16 KB float pow-10 tables, or per-struct hash tables.This is a follow-up to the discussion in #2614 and folds in the error-formatting fix from #2620. It reuses the existing value-based
optimization_levelmachinery rather than adding a parallel#ifdefinteger-only path.Mechanism
optimization_level.hpp— adds an exportedglz::default_optimization_levelconstant, fed byGLZ_DEFAULT_OPTIMIZATION_SIZE(boolean knob) orGLZ_DEFAULT_OPTIMIZATION_LEVEL(explicit level, advanced).opts.hpp— routes thecheck_optimization_levelandcheck_linear_searchfallbacks through that constant, so integers, floats, and hash-table elimination flip together. Explicit per-call options still win: anoptimization_level::normalkeeps the fast tables even in a size-default build, so the only path back to the large tables is explicit and greppable. The default (normal) build is byte-for-byte unchanged.CMakeLists.txt—glaze_DEFAULT_OPTIMIZATION_SIZEoption injects the macro as anINTERFACEcompile definition (build-wide, ODR-consistent).Internal leak fixes (so size users actually get the guarantee)
validate.hpp—glz::format_errornow formats index/line/column withopts_size{}(these aresize_tonly; error formatting is never a throughput path). This is the specific footgun raised in Add cmake option to optimize for size #2614 and the change from Use size optimization for error formatting #2620.jsonb/write.hpp— BEVE float writes preserve the caller's optimization level instead of hardcoding normal opts, so size-optimized BEVE writes drop the float pow-10 tables.C++20 modules
default_optimization_levelis a value consumed viaif constexpr (is_size_optimized(Opts)), never a preprocessor branch inside serialization code. When Glaze gains a module interface, the default is baked once at module-build time and consumed everywhere by name, while per-callglz::optsselection (the only behavior driver) composes withimport glaze;with zero macros.Verification
tests/size_optimization_test:static_assert): a bareopts{}inherits the build default for ints/floats/linear-search;opts_size{}is always size; an explicitnormalstill wins.nmthat a size-default binary does not link the 40 KBdigit_quadstable, so a future hardcoded-normalregression fails CI. (Verified locally: size build has 0 such symbols, a normal control has 1.)Built and ran locally (AppleClang, Debug):
size_optimization_test,size_optimization_size_default,size_optimization_symbols, plusjson_test,beve_test,lazy_beve_test,jsonb_test,csv_test,float_write_testall pass.Closes the design question in #2614; supersedes #2620.