Repository navigation
feat(rust): add the RC4 Rust extractor framework (ADR-1713) - #2086
Merged
Merged
Conversation
This was referenced Oct 5, 2026
12 of 18 tasks
lusoris
force-pushed
the
rc4/rust-extractor-framework
branch
from
October 7, 2026 14:26
e99e0f4 to
5962a1a
Compare
lusoris
marked this pull request as ready for review
October 7, 2026 14:26
lusoris
force-pushed
the
rc4/rust-extractor-framework
branch
from
October 7, 2026 15:19
5962a1a to
e8886a9
Compare
…y tester leg before a cut (ADR-2198) (#2409) * ci: build the Windows SYCL zip where its inputs change and check every tester leg before a cut (ADR-2198) The x64 SYCL tester zip was red on master for two days with nothing required noticing. A pull request now builds it when an input it reads changes, in the light tier through own_input_lanes, and the cut pull request runs scripts/release/check-candidate-legs.py. * docs: regenerate the indexes and the citation map after rebasing
* feat(rust): add the RC4 Rust extractor framework crates and the psnr reference twin
The workspace gains vmafx-fex (the ABI of the Rust twins, the Extractor
trait, option, picture and host views, libm wrappers), the reference twin
vmafx-fex-psnr, empty crates for the speed_chroma, motion, adm, cambi and
prediction lanes, and vmafx-core-rs, the one Rust archive libvmaf links.
The TAD pilot becomes an rlib of that archive and drops its unused
build-time cbindgen dependency, so the Rust build needs no network. The C
header of the ABI is generated by scripts/dev/rust-abi-header.sh and
committed. ADR-1713.
* feat(rust): register the Rust twins through a C shim and select them with VMAF_FEATURE_IMPL
core/src/rust/shim/rust_twins.cpp turns each Rust twin into a
VmafFeatureExtractor named <c name>_rust that copies the C extractor's
descriptor (options, priv size, provided features, flags) and drives the
Rust entry points with the parsed options, plane views, the previous
references and the C feature collector. feature_extractor.cpp walks the
static list and then the Rust extractors the shim installs at vmaf_init(),
skips Rust twins in plain feature-name lookups, audits them like device
twins and offers vmaf_feature_extractor_impl_select(), which libvmaf
applies on every registration path when VMAF_FEATURE_IMPL=rust.
Meson builds one archive (vmafx-core-rs) with an offline cargo build and
a depfile, links it into libvmaf only, and keeps its symbols out of the
dynamic symbol table with --exclude-libs. The TAD pilot is now registered
in Rust builds; before, feature_extractor.cpp never saw HAVE_RUST_TAD.
scripts/ci/rust_twin_diff.py runs both implementations of one binary and
requires equal doubles on every metric and a receipt naming the twin; the
psnr reference twin is equal on the Netflix pair, both checkerboard pairs,
the 10-bit sparks pair and 200 frames of 4K. ADR-1713.
* feat(rust): give the Rust twins a host log and a log-only close
Lane request S-1: VmafxRsHost gains a `log` callback (appended, so no
offset moves) and the twin's close entry point receives a host whose
collector callbacks fail but whose log works, so a twin can report what
its C extractor logs at close. Extractor::close() runs before the state
drops; Host::log_fmt formats into a stack buffer without allocating.
Lane request P-1: the header script also generates
core/src/rust/include/vmafx_rs_predict.h once the predictor's cbindgen
configuration exists, and vmaf_feature_impl_rust_requested() is the one
reader of VMAF_FEATURE_IMPL for every Rust path.
The build wrapper keeps a plain subprocess call: the safe_subprocess
helper resolves symlinks and a rustup cargo is a symlink to rustup. The
shim asserts its preconditions (Power-of-10 rule 5) and propagates the
dictionary free result instead of discarding it.
* docs(rust): document the Rust extractor framework and gate it in the Rust workflow
docs/development/rust-extractor-framework.md explains how to build and
select the Rust path, how a twin is registered and written, the arithmetic
rules for bit identity, the differential harness and its fixtures, and the
tests. VMAF_FEATURE_IMPL joins the environment variable reference; the
build-flag, Rust guide and TAD pages follow the new build. A core/src
AGENTS.d page records the invariants of the framework, the TAD crate's
AGENTS.md its new shape.
The Rust workflow runs fmt and clippy on every workspace crate, checks the
cbindgen header, builds libvmaf with the Rust extractors, runs the rust
Meson suite and the harness on the Netflix and checkerboard pairs; the
impact selector covers the new paths and the Meson runner inventory lists
the workflow. docs/state.md records the two defects fixed on the way and
the missing container toolchain.
* fix(rust): format the host text buffer and record the psnr twin's provenance
Lane request C-1: cargo fmt --all --check failed on the StackText literal
in vmafx-fex's host.rs. Lane request A-1: the reference twin ports
integer_psnr.c statement by statement, so relicense_provenance.toml gives
it the netflix-integer-psnr family (documented-port instead of
provenance-unreviewed); each RC4 lane adds the block of its own crate.
* build(rust): give the libvmaf-linked crates a workspace without external crates
An offline cargo build from an empty CARGO_HOME failed in the root
workspace ("failed to select a version" for bindgen, required by
vmafx-sys): cargo resolves the whole workspace even for one package. The
framework crates, the twins, the predictor, vmafx-core-rs and the TAD
crate now form core/src/rust/Cargo.toml, whose lockfile holds only those
nine crates; the root workspace keeps the bindings and excludes both
directories. Meson builds against the new manifest, and the Rust workflow
runs fmt, clippy and tests on both workspaces and builds vmafx-core-rs
offline from an empty CARGO_HOME, which fails as soon as a crate there
gains an external dependency. The TAD rebuild step went with TAD's
build.rs.
* fix(rust): initialise a Rust twin's shared context before a threaded flush
Lane request M-1: with --threads N, flush_non_temporal_cpu_extractors()
calls flush() on the shared context of a non-temporal extractor, which is
never initialised (only the per-thread copies are). The C extractors'
flush needs no init state; a Rust twin's flush found no instance and the
run failed with "problem flushing context". init_shared_rust_twin()
initialises that context with the run's picture parameters first. Lane M
measured motion_rust with the five-frame window equal to C at --threads 1
and 4 with the change, failing without it.
Lane request A-2: the harness runs every cell once per --threads value
(default 0,1), and a cell where both sides refuse the input with the same
exit status is REFUSED and passes (speed_chroma at prescale 0.5 refuses
480x270 in C), while one side refusing alone stays an error.
* test(rust): run every twin cell serially and with thread pools of 1 and 4
The flush of a non-temporal extractor runs on the shared context only with
a thread pool, so the harness default becomes --threads 0,1,4 and the rust
Meson suite's harness test runs all three. motion and motion_v2 are the only
non-temporal C extractors with a flush; motion_rust (lane M) is the twin
that exercises init_shared_rust_twin() and failed at --threads 1 and 4
without it.
* docs(rust): give each RC4 twin its own row in the framework guide
Lane request A-3: one row per twin, so the PR that lands a twin edits only
its own row.
* fix(rust): hold the framework to the licence and tidy-coverage gates master gained
Two gates landed on master after the framework branched:
- The package licence check (ADR-1699) wants each crate's `license` to be
the AND of the licences of the files it ships. Every crate of the
libvmaf-linked workspace ships the root README.md (BSD-2-Clause-Patent in
REUSE.toml) next to its EUPL-1.2 sources, and the psnr twin carries
statements ported from integer_psnr.c, so the workspace declares
"EUPL-1.2 AND BSD-2-Clause-Patent", as vmafx-tad already does.
- The clang-tidy coverage rule wants every tracked translation unit read by
a lane or named in an exception. The shim and its two tests build only
with -Denable_rust_features=true, and the dev image every lane runs in
has no Rust toolchain yet, so they get the entry tad_rust.c and
test_tad_rust.c already have, with the same expiry, until a pinned
toolchain is in dev/Containerfile and a lane reads them.
* docs(adr): accept ADR-1713 for the RC4 Rust extractor framework (Q-087)
The maintainer accepted the framework decision as written (ledger Q-087,
2026-10-07): Rust twins registered next to the C extractors as
`<name>_rust`, selected with VMAF_FEATURE_IMPL, one Rust archive, the C ABI
unchanged. The status line, the deciders and the index row say so, and the
References cite the ledger entry.
* fix(test): return the TAD test's failure message without discarding const
expect_score() took its message as `const char *` and returned it as the
`char *` the test runner expects, which GCC reports as
-Wdiscarded-qualifiers. The file builds only with
-Denable_rust_features=true, so no warning-checked leg compiled it before
the Rust framework gate. The message is a string literal like every other
message the runner returns; it is now passed as `char *`.
* test(build): follow the TAD pilot into the Rust framework in the stale-text contract
The contract that landed on master (#1976, defect 18) checks that a build
without enable_rust_features neither compiles tad_rust.c nor registers
TAD, and it spelled that through the pilot's own Meson list
(rust_tad_direct_sources) and its #if HAVE_RUST_TAD registry entry. The
framework (ADR-1713) replaced both: tad_rust.c is a direct libvmaf source
inside the Rust block next to the shim, and the shim registers the pilot.
The test now reads that shape: the source is listed once, inside the Rust
block, the shim assigns vmaf_fex_tad, and the static registry does not name
it. Moving the source out of the Rust block fails the test.
* fix(rust): give the VMAF_FEATURE_IMPL selector enum a one-byte underlying type
clang-tidy 22.1.8 (cpu lane, dev container) reported performance-enum-size on enum class FeatureImpl in core/src/feature/feature_extractor.cpp: three enumerators fit std::uint8_t. The lane now reads 0 findings in feature_extractor.cpp and libvmaf.c.
* build(docker): copy the Rust twin harness into the tester image's build stages
Master's tester image contract (tools/rc1-tester/tests/
test_dockerfile_script_imports.py) requires every Meson build stage to copy
each file outside core/ that the Meson tree names; the framework's rust
suite names scripts/ci/rust_twin_diff.py, so the vmaf, SYCL, CUDA and HIP
build stages copy it. The test failed without the four lines.
* docs(adr): regenerate the ADR index pages after the rebase onto master
* docs(adr): regenerate the generated files after the rebase onto master
* docs: regenerate the indexes and the citation map after rebasing
42 tasks
lusoris
force-pushed
the
rc4/rust-extractor-framework
branch
from
October 7, 2026 16:43
e8886a9 to
ccf7ccf
Compare
This was referenced Oct 7, 2026
lusoris
added a commit
that referenced
this pull request
Oct 8, 2026
…after the library split WP6 (ADR-2094) removed libvmaf.get_static_lib(): libvmaf is the compat library, and white-box tests link the engine through vmaf_test_link. test_rust_twin_registry (framework #2086, on master) still asked for the static libvmaf, so meson setup failed with -Denable_rust_features=true. The citation map loses the compat_libvmaf_gen.c sites the split removed and gains the sites of the restack. Signed-off-by: Lusoris <lusoris@proton.me>
13 of 18 tasks
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
This is the shared part of RC4 (#1723, task 1): the framework that the
speed_chroma,motion,adm,cambiand prediction lanes build on. Each Rust extractor becomes a twin of a C extractor. It is registered as<name>_rustnext to the C extractor, inherits its options, feature names and flags, and is selected at run time withVMAF_FEATURE_IMPL=rust. The C extractors stay the default and the fallback, andlibvmaf.hand every exported symbol are unchanged. The reference twinpsnr_rustreturns the C scores bit for bit on every fixture (results below).Lands bottom-up per Q-083:
v1.0.0-rc.3is tagged, so RC4 lands; this is the first PR of the Rust stack, and the twins #2085, #2090, #2096, #2097, #2099 follow once it is on master. ADR-1713 is accepted as written (ledger Q-087, 2026-10-07; status, deciders, index row and References updated in this PR).Rebase onto master (
03584029c, after #2173 landed; the merge train stopped on itscore/src/libvmaf.cinclude block, resolved keep-both:vmafx/engine.hfrom #2173 next to the Rust shim header; 200+ commits since the draft): conflicts resolved per hunk (core/src/meson.build: master's SpEED sources andzimg_dependencykept next torust_core_dep;rust-ci.yml: master's--workspacecomment merged into the two-workspace clippy step;docs/state.mdby the row resolver; generated ADR / citation files regenerated). Three gates master gained meanwhile needed changes here, each its own commit:Package licence check (ADR-1699): every crate of
core/src/rustships the rootREADME.md(BSD-2-Clause-Patent inREUSE.toml) and EUPL-1.2 sources, and the psnr twin ports Netflix statements, so the workspace declareslicense = "EUPL-1.2 AND BSD-2-Clause-Patent", asvmafx-tadalready does.clang-tidy coverage (ADR-1762):
rust_twins.cpp,test_rust_abi_layout.candtest_rust_twin_registry.cbuild only with-Denable_rust_features=true, which no lane can configure while the dev image has no Rust; they get the exceptiontad_rust.candtest_tad_rust.calready have (same reason, expiry 2026-12-31).Stale-text contract (fix(docs): make help, header, option, CI and gate texts say what the code does #1976): its TAD check spelled the pilot's old Meson list and registry entry; it now checks the framework's shape (source listed once, inside the Rust block; the shim registers it; the static registry does not). Moving the source out of the Rust block fails it.
Tester image contract (
tools/rc1-tester/tests/test_dockerfile_script_imports.py): the vmaf, SYCL, CUDA and HIP build stages ofdocker/Dockerfile.testercopyscripts/ci/rust_twin_diff.py, which therustMeson suite names.clang-tidy (cpu lane):
enum class FeatureImplgets astd::uint8_tunderlying type (performance-enum-size).core/test/test_tad_rust.cprinted-Wdiscarded-qualifiersin a Rust build (master's file, compiled only with Rust): fixed, so the Rust leg is warning-free (0 warnings with warnings as errors, Q-061).What changes:
core/src/rust/Cargo.toml, separate from the root workspace of the bindings, so its lockfile holds no external crate and the build runs offline from an emptyCARGO_HOME(cargo resolves the whole workspace even for-p, andvmafx-sysneedsbindgen).vmafx-fexholds the ABI, theExtractortrait, option, picture and host views, and the libm wrappers.vmafx-fex-psnris the reference twin. The lane crates (speed,motion,adm,cambi,predict) start empty.vmafx-core-rsis the one Rust archive libvmaf links. The TAD pilot becomes an rlib of it and drops its unused build-time cbindgen dependency, so the cargo build runs--offline --locked.core/src/rust/shim/rust_twins.cppcopies each C descriptor and drives Rust with the parsed options, plane views, previous references and the C collector.feature_extractor.cppwalks the static list, then the Rust extractors the shim installs atvmaf_init(). Plain feature-name lookups skip Rust twins.vmaf_feature_extractor_impl_select()appliesVMAF_FEATURE_IMPLon every registration path.core/src/rust/include/vmafx_rs.his generated by cbindgen (scripts/dev/rust-abi-header.sh) and committed.test_rust_abi_layoutcompares every size and offset.scripts/ci/rust_twin_diff.pyruns one binary withcand withrustand requires equal doubles on every metric of every frame, plus a JSON receipt naming the twin. The cells come from the option sets of thevmaf_v1.0.16models, and each runs at--threads 0,1,4. Both sides refusing an input with the same exit status counts as parity (REFUSED).flush_non_temporal_cpu_extractors()flushes it (init_shared_rust_twin()). C flushes need no init state, which is why this never surfaced.Rustworkflow runs fmt and clippy on every workspace crate (today:vmafx-sysonly), checks the header, builds libvmaf with the Rust extractors, and runs the newrustMeson suite and the harness.docs/state.md. First, TAD was never registered in a Rust build. Second, a Rust build exported the Rust standard library fromlibvmaf.so; the archive is now linked with--exclude-libs.Lane requests applied on this branch: S-1 (host log, log-only close), P-1 (predictor header, env resolver), M-1 (threaded flush), A-1 (provenance family), A-2 (refusal parity), A-3 (one guide row per twin) and C-1 (fmt). The shared contract is
docs/development/rust-extractor-framework.md.Type
feat— new featurefix— bug fixperf— performance improvementrefactor— no behavior changedocs— documentation onlytest— test-onlybuild/ci— tooling / infraport— cherry-pick from upstream Netflix/vmafsycl/cuda/simd— backend-specificChecklist
make format && make lintis green locally. Commit and pre-push hooks pass: clang-format, black, ruff, mypy, shfmt, semgrep, assertion density and HISS audit.cargo fmt --all --checkandcargo clippy --workspace --all-targets -- -D warningsare clean.python3 scripts/ci/run_meson_test.py -- -C build-rs --suite=fast --suite=rust→ 380 OK including the threerusttests, 0 fail (-Denable_rust_features=true -Db_lto=false,-j4).cargo test --manifest-path core/src/rust/Cargo.toml --workspacepasses;cargo fmt --checkandcargo clippy --workspace --all-targets -D warningsare clean on both workspaces; both lock files load withcargo metadata --locked --offline./cross-backend-diffand the worst ULP is ≤ 2. No SIMD or GPU path is touched.psnr_rustonly readsinteger_psnr.c..c/.cpp/.cu/.h/.hpp, it has the appropriate license header (seeCONTRIBUTING.md). Fork-authored files areEUPL-1.2. The psnr twin isBSD-2-Clause-Patentbecause it ports Netflix code statement by statement (ADR-1250).!orBREAKING CHANGE:and the migration path is documented below. Not breaking.docs/adr/_index_fragments/<NNNN-slug>.mdand the slug is appended todocs/adr/_index_fragments/_order.txt— do not editdocs/adr/README.mddirectly (regenerated byscripts/docs/concat-adr-index.sh; see ADR-0221).Bug-status hygiene (ADR-0165)
docs/state.md:T-RUST-TAD-NEVER-REGISTERED-2026-10-05andT-RUST-STD-SYMBOLS-EXPORTED-2026-10-05are opened and closed here.T-RUST-DEV-CONTAINER-TOOLCHAIN-2026-10-05is opened (RC4).Netflix golden-data gate (ADR-0024)
assertAlmostEqual(...)score in the Netflix golden Python tests.Cross-backend numerical results
psnr_rustagainstpsnron the same binary at--precision max. Every value is equal as an IEEE double, and the receipt namespsnr_rust:Re-run on the rebased head:
rust_twin_diff.py --all-twins --fixtures netflix,checker1,checker10,sparks10,bbb4k --threads 0,1,4→ 15 of 15 cells EQUAL (bbb4k all 200 frames). The model cells (--models,--threads 0) are equal on netflix, checker1 and checker10 (24 of 24). On sparks10 they are equal except the two*_3d0h_2160models, which both sides refuse (SpEED prescale 0.5 leaves 480x270 too small). No model feature has a twin on this branch yet, so these cells test the selection and fallback path, not a twin.Planted-defect check: a 1-ulp change in the twin (
* (1.0 + f64::EPSILON)) makes the harness exit 1 and print the differing frames in ulp. A hand edit of the generated header makesrust-abi-header.sh --checkexit 1.Performance (if
perforfeat)No performance claim. Benchmarks belong to RC8 (ADR-1490). The default build runs no Rust code.
Deep-dive deliverables (ADR-0108)
## Alternatives considered(twins vs replacing entry points, cbindgen at build time vs committed vs hand-written, panic strategy, selection surface).AGENTS.mdinvariant note —core/src/AGENTS.d/rust-extractor-framework.md(new page, index regenerated) andcore/src/feature/rust/AGENTS.md.changelog.d/added/rust-extractor-framework.md,changelog.d/fixed/rust-build-tad-registration-and-symbols.md.docs/rebase-notes.md.Reproducer
Local gate on the rebased head
-Denable_rust_features=true -Db_lto=false,-j4, warnings as errors: 0 warnings. Default build (Rust off): 0 warnings,--suite=fast380 OK, 0 fail.run_meson_test.py --suite=fast --suite=rust: 384 OK (the threerusttests included), 0 fail.make test-netflix-golden GOLDEN_NINJA_JOBS=4, Rust off as on every default build): 280 passed, 3 skipped.--threads 0,1,4, bbb4k 200 frames).cargo fmt --check,cargo clippy --workspace --all-targets -D warnings,cargo test --workspaceon both workspaces: clean;cargo metadata --locked --offlineloads both lock files.core/src/feature/feature_extractor.cpp,core/src/libvmaf.c0 findings (dev container, clang-tidy 22.1.8).rust_twins.cppand the two Rust tests: no lane can build them yet (exceptions above). GPU lanes not measured: the changed lines of the two shared files are outside every backend block.scripts/dev/preflight.sh --stage msvcism: pass.pre-commit run --filesover every changed file: pass. Train gates (deliverables, state-md touch and rows, silent revert,praetorctl audit): pass.Known follow-ups
--feature_impl, decides the default ofenable_rust_featuresand records throughput against the C path.T-RUST-DEV-CONTAINER-TOOLCHAIN-2026-10-05: a pinned Rust toolchain indev/Containerfile, needed before published artifacts can carry the Rust path.ld64has no--exclude-libs) and Windows linking (the archive's native libraries) of the Rust archive.scripts/dev/rust-abi-header.sh --checkskipped locally (no cbindgen on this host; the script says so);test_rust_abi_layoutpassed. The Rust workflow runs the check.