Design specs: provenance tracking and the generated
.gloam/manifest.jsonare specified in docs/manifest.md (consumer-facing format) and docs/provenance-internals.md (producer internals: acquisition, cache, bundler, header layout).
The pipeline is strictly linear: parse -> IR -> resolve -> generate.
CLI args
-> fetch/bundled XML specs
-> parse into RawSpec (IR)
-> resolve into FeatureSet (indexed, sorted, grouped)
-> generate C code via minijinja templates (or Rust via string emission)
| Module | Purpose |
|---|---|
main.rs |
Entry point; orchestrates CLI -> resolve -> generate |
cli.rs |
clap-derived CLI definitions, ApiRequest parsing, extension filter parsing |
ir.rs |
Raw intermediate representation types directly from XML (pre-resolution) |
bundled.rs |
Compile-time-embedded XML specs and auxiliary headers (include_str!) |
fetch.rs |
Load specs from bundled copies or remote Khronos URLs (--fetch) |
build_info.rs |
Git version metadata embedded at compile time (generated by build.rs) |
preamble.rs |
Copyright/license/provenance comment block for generated files |
regen.rs |
gloam regen: replays each tree's recorded command line to regenerate it in place |
parse/ |
XML -> RawSpec IR |
parse/mod.rs |
Orchestrator; raw C text extraction, enum value computation |
parse/types.rs |
<types> -> RawType[] with topological sort (Kahn's algorithm) |
parse/enums.rs |
<enums> -> flat enums + Vulkan typed enum groups |
parse/commands.rs |
<commands> -> RawCommand[] with alias-chain fixup and Vulkan scope inference |
parse/features.rs |
<feature> + <extension> -> RawFeature[] + RawExtension[] |
resolve/ |
RawSpec + CLI args -> FeatureSet |
resolve/mod.rs |
Three-phase orchestrator (selection -> materialization -> grouping) |
resolve/types.rs |
Public output types: FeatureSet, Feature, Extension, Command, etc. |
resolve/selection.rs |
Which features/extensions are "in" (filter, promoted, predecessors) |
resolve/requirements.rs |
Collects required types/enums/commands from selected features+extensions |
resolve/commands.rs |
Indexed Command entries, PFN ordering optimization, alias pair extraction |
resolve/pfn.rs |
PFN range tables (feature -> command index ranges) |
resolve/enums.rs |
Flat enums and Vulkan enum groups for the resolved set |
resolve/typedefs.rs |
Type list with dependency ordering and include-guard inference |
resolve/protect.rs |
Platform protection lattice; group-by-protection coalescing |
resolve/spec_info.rs |
Spec-level constants: display names, PFN prefixes, name prefixes |
generator/ |
FeatureSet -> output files |
generator/c/mod.rs |
C generator: template rendering, function-name blob layout, aux header copying |
generator/c/templates/ |
Minijinja templates (header.h.j2, source.c.j2, hash_search.j2, library.j2, loader.j2, etc.) |
generator/rust/mod.rs |
Rust generator (experimental; GL/GLES): emits a self-contained #![no_std] crate by string emission |
RawSpec(ir.rs): Everything parsed from one XML spec family (types, enums, commands, features, extensions). This is the pre-resolution IR.FeatureSet(resolve/types.rs): Fully indexed, sorted, protection-grouped output ready for template rendering. Contains features, extensions, commands, types, flat enums, enum groups, PFN range tables, extension index subsets, and protection-grouped lists.Command(resolve/types.rs): Indexed command with short name, PFN type name, parameter string, scope (for Vulkan), and protection.Protection(resolve/protect.rs):UnconditionalorGuarded(macros)— used to emit#ifdefblocks in generated code.
- Selection: Determine which API features and extensions are included
based on CLI flags (
--api,--extensions,--promoted,--predecessors,--baseline). - Materialization: Build indexed arrays of commands, types, enums. Optimize command ordering to minimize PFN range fragmentation. Build PFN range tables.
- Grouping: Coalesce items by platform protection for
#ifdef-correct header emission.
At load time, the generated code detects driver-supported extensions without any string comparisons:
- Calls
glGetIntegerv(GL_NUM_EXTENSIONS, &n)to get the count. - Calls
glGetStringi(GL_EXTENSIONS, i)for eachi, hashes each name with XXH3-64 (the same algorithm used at generator time), and stores the hashes in a heap-allocateduint64_t[]. - Shellsorts the array in-place (Ciura gap sequence — no extra memory, ~160 bytes of code).
- Binary-searches the sorted driver hashes against the pre-baked known extension hash table embedded in the generated source.
This gives O(n log n) total work to detect all extensions, with O(log n) per lookup.
When --alias is passed, the generated loader emits a runtime resolver:
after loading all function pointers, if the canonical slot for an alias
pair is null but the alias slot was loaded by the driver (or vice versa),
the loaded pointer is propagated to both slots. This handles the case
where a driver only exposes one spelling of a promoted function.
--alias is a runtime concern — it does not affect which extensions
are selected. For selection-time alias expansion see --promoted and
--predecessors.
All function names are packed into a single kFnNameData[] string
table with pre-computed offsets in kFnNameOffsets[]. This is indexed
in lockstep with pfnArray[] so that loading code can look up the name
for any function pointer slot by index.
Features and extensions map to contiguous ranges of command indices.
The tables are contiguous-run compressed: each entry is a
(start_index, count) pair. This allows bulk-loading all function
pointers for a feature or extension with a single loop over the range.
Extensions that require platform-specific headers are wrapped in
#ifdef blocks (e.g. VK_USE_PLATFORM_WIN32_KHR). The resolver
groups consecutive items with identical protection into
ProtectedGroup<T> to minimize the number of #ifdef/#endif pairs
in the generated output.
The XML specs have numerous inconsistencies that the parser handles explicitly. These are documented in code comments as "Spec gotcha #N":
- Command alias entries lack full prototypes — walk alias chains to copy signatures
- Type and enum dependency ordering requires topological sort
- Vulkan enum groups can be empty after filtering — prune them
- Bitwidth=64 must propagate through enum alias chains
- GL has auto-excluded types that should be silently skipped
- Some XML contains C++
//comments — rewrite to/* */for C99 - macOS needs a special
ptrdiff_tguard for GL pointer-sized types GLX_SGIX_video_sourceandGLX_SGIX_dmbufferare broken — silently dropWGL_ARB_extensions_stringis mandatory but might be missing — warn- Vulkan
<enums type="enum"|"bitmask">should not be re-processed as flat constants - Vulkan command scope is inferred from first parameter type (Global/Instance/Device)
- Duplicate enum names with conflicting values must be detected and rejected
Integration tests live in tests/. They invoke the binary via
assert_cmd, generate into temp directories, and optionally compile the
output with cc when available. Shared helpers (the gloam runner, compile
check, header/source readers) live in tests/common/mod.rs.
| Test file | Coverage |
|---|---|
generate_c.rs |
GL/GLES C loader generation + compilation |
generate_rust.rs |
Rust loader crate structure, constant typing, cargo check |
generate_vulkan.rs |
Vulkan-specific generation |
generate_wgl_glx.rs |
WGL, GLX, and cross-API edge cases |
predecessor_promoted.rs |
--promoted and --predecessors flag behavior |
provenance_manifest.rs |
Implicit provenance baseline on regeneration |
integration_coverage.rs |
Determinism, exclusions, baselines, flag matrix |
golden.rs |
Byte-exact snapshots of generated output (see below) |
Run the full suite:
cargo testtests/golden.rs compares generated .h/.c output (preamble stripped)
byte-for-byte against checked-in snapshots under tests/golden/, one config
per spec family plus the merged GL+GLES2 build, plus two Rust-backend
configs (Cargo.toml + src/lib.rs). A refactor that should not
change output is proven neutral by cargo test. When output changes
deliberately — or a bundle refresh changes resolved content — re-bless and
review the diff:
GLOAM_BLESS=1 cargo test --test golden
git diff tests/golden/The library unit tests run clean under Miri and should stay that way:
cargo +nightly miri test --no-default-features --libExpect the first run to be slow (Miri builds its own sysroot). Scope is
--lib only: the integration tests spawn the gloam binary as a
subprocess, which Miri cannot interpret. Note this checks gloam itself,
not the generated loaders — their dispatch paths call driver FFI, which
no interpreter can execute; the generated crate's soundness-sensitive
choices (Option transmutes, explicit unsafe blocks, checked dispatch)
are enforced structurally at generation time instead.
gloam regen [paths...] regenerates existing output trees in place by
replaying the command line recorded in each tree's
.gloam/manifest.json. A path may be a tree root, a directory to search
recursively (the default is .), or a manifest file itself. The effective
output path is derived from each manifest's own location — the recorded
--out-path was relative to the original invocation's cwd, which is
unknowable later — and the recorded command line is re-recorded verbatim,
so regeneration works from any directory and never rewrites what the
manifest says produced the tree.
By default each replay is pinned to the tree's recorded provenance
(--lock semantics), so git diff shows only the effect of gloam code
changes; pass --fresh to re-resolve sources instead (advancing the tree
to the current bundle, or to upstream HEAD if the recorded command used
--fetch).
cargo xtask regen [tree-root] [--fresh] builds the working-copy gloam
and runs it over tree-root (default: the current directory) — use this
for examples/ here or a gloam-pregen checkout. To review a regen,
filter out gloam's own version/commit stamp churn:
git diff -I'^ \* @generated by gloam ' -I'^// @generated by gloam ' \
-I'^ "(version|describe|commit)": ' -I'^ "blob": '(Ignoring 6-space "blob" lines is safe: a provenance pin's blob never
moves without its unfiltered sibling "commit" line moving too, so real
source changes always stay visible; the output BOM's blob lines just track
stamp churn.)
Set GLOAM_DEBUG=1 to trace provenance/fetch activity on stderr: every
HTTP request (URL, status, latency), every load::resolve call (mode, key
count, duration), and engine construction / bundle-seed timings.
Independent of --quiet. Use this to answer "when and why is gloam
hitting the network or the cache?"
- Determinism:
IndexMapis used throughout for insertion-order preservation. Never introduceHashMapiteration or other sources of non-determinism. - Self-contained binary: XML specs and auxiliary headers are embedded via
include_str!frombundled/. - Progress output: goes to stderr, respects
--quiet. Only errors useeprintln!unconditionally. - Preamble: generated files include the exact command line, gloam version, extension selection summary, and license notices.
- Minimal changes: prefer small, focused changes. Don't over-abstract or add speculative infrastructure.