Skip to content

Add build-wide default optimization level (size) option#2633

Merged
stephenberry merged 2 commits into
mainfrom
feature/default-optimization-level
Jun 16, 2026
Merged

Add build-wide default optimization level (size) option#2633
stephenberry merged 2 commits into
mainfrom
feature/default-optimization-level

Conversation

@stephenberry

Copy link
Copy Markdown
Owner

Summary

Lets embedded users make size the default optimization level for the whole build, instead of remembering to pass 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 40 KB integer digit_quads table, 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_level machinery rather than adding a parallel #ifdef integer-only path.

Mechanism

  • optimization_level.hpp — adds an exported glz::default_optimization_level constant, fed by GLZ_DEFAULT_OPTIMIZATION_SIZE (boolean knob) or GLZ_DEFAULT_OPTIMIZATION_LEVEL (explicit level, advanced).
  • opts.hpp — routes the check_optimization_level and check_linear_search fallbacks through that constant, so integers, floats, and hash-table elimination flip together. Explicit per-call options still win: an optimization_level::normal keeps 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.txtglaze_DEFAULT_OPTIMIZATION_SIZE option injects the macro as an INTERFACE compile definition (build-wide, ODR-consistent).

Internal leak fixes (so size users actually get the guarantee)

  • validate.hppglz::format_error now formats index/line/column with opts_size{} (these are size_t only; 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_level is a value consumed via if 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-call glz::opts selection (the only behavior driver) composes with import glaze; with zero macros.

Verification

tests/size_optimization_test:

  • Compiles the wiring checks in both modes (static_assert): a bare opts{} inherits the build default for ints/floats/linear-search; opts_size{} is always size; an explicit normal still wins.
  • Asserts via nm that a size-default binary does not link the 40 KB digit_quads table, so a future hardcoded-normal regression 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, plus json_test, beve_test, lazy_beve_test, jsonb_test, csv_test, float_write_test all pass.

Closes the design question in #2614; supersedes #2620.

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.
@stephenberry stephenberry merged commit 60e8aec into main Jun 16, 2026
96 of 97 checks passed
@stephenberry stephenberry deleted the feature/default-optimization-level branch June 16, 2026 17:12
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant