jevsnes.git / apps / hotdemo / CLAUDE.md
1@README.md
2
3- The flags on this target (`rustc_flags`, `linker_flags` in `BUCK`) are the
4  whole point of the crate - do not "clean them up". Removing
5  `-Cdebug-assertions=yes` makes `subsecond::call` a no-op silently (it still
6  compiles and runs, it just never patches); removing `-Csave-temps=true`
7  removes the `.rcgu.o` objects `tools/hotpatch` links into the patch.
8- **`-Cdebug-assertions=yes` on THIS target is not enough by itself** -
9  `subsecond::call`/`try_call` are `cfg!(debug_assertions)`-gated
10  (subsecond/src/lib.rs:250-254, :411-414), and `cfg!()` resolves against
11  whichever crate's OWN compilation the macro invocation lives in, not the
12  downstream consumer's. Under cargo, one profile setting covers the whole
13  dependency graph; under buck2/reindeer, `//third-party/rust:subsecond` is
14  its OWN independent rustc invocation, built at the toolchain's default
15  (no debug-assertions) unless told otherwise. That's what
16  `third-party/rust/fixups/subsecond/fixups.toml`'s `rustc_flags =
17  ["-Cdebug-assertions=yes"]` is for. Measured 2026-09-20: without it,
18  `apply_patch` returns `Ok` and "patch applied" prints, but `nm` on the
19  patched binary shows zero calls to subsecond's own `get_jump_table` from
20  anywhere in `main` - the whole redirect was silently inert.
21- **`subsecond::call` needs a bare `fn` item, not a closure that captures
22  anything** - `tick` takes no arguments and reads a `static AtomicU64`
23  instead of a closure-captured `n`, and `main` calls `subsecond::call(tick)`
24  directly, never `subsecond::call(|| tick(n))`. Two independent reasons,
25  both measured 2026-09-20, discovered together as apparent inlining but
26  actually two separate causes:
27  1. `HotFn::try_call`'s redirect key depends on `size_of::<F>()`
28     (subsecond/src/lib.rs:420 on): a closure whose captured environment
29     happens to be exactly pointer-width (one `u64`, one reference, …) takes
30     the "treat this value as a bare fn pointer" branch
31     (`call_as_ptr`/`transmute_copy::<Self, Self::Real>`), which is only
32     correct for an ACTUAL bare fn pointer - for a real capturing closure of
33     that size it transmutes captured DATA as if it were a function address.
34  2. At this project's forced `-Copt-level=3` (toolchains/BUCK), rustc's
35     default cross-codegen-unit ThinLTO (visible as the
36     `*.rcgu.thin-lto-*.bc` files next to the `.rcgu.o`s) inlines a
37     small, single-call-site function into its caller. `subsecond`'s crate
38     carries no `#[inline(never)]` anywhere (it's written assuming a
39     dev-profile-style build where little gets inlined); without
40     `#[inline(never)]` on `tick` itself, `tick`'s own body - and the whole
41     `subsecond::call`/`HotFn`/`HotFunction::call_it` chain around it -
42     collapsed into one block inside `main` with no addressable symbol left
43     to redirect a call to.
44  If either fix is missing, `apply_patch` still succeeds and prints nothing
45  wrong - the log just keeps printing the old string forever. That silence
46  is what makes both traps worth restating here rather than only in the
47  research doc.
48- `tick`'s string literal is the thing this crate exists to let you change
49  without restarting the process. Editing anything else in `main.rs` is a
50  bigger, unproven patch (research/subsecond-patch-build.md's workspace-replay
51  path, §2/§5) - keep edits here to that one function while proving the
52  pipeline.
53- `TICKS` resets to 0 on every patch, on purpose - it's part of the tip
54  crate's own recompiled objects, not carried over from an unchanged
55  dependency crate, so subsecond's "globals are tracked" claim
56  (subsecond/src/lib.rs's module doc) doesn't apply to it. Don't read this
57  as a bug to fix; it's the same limitation as the thread-local one that
58  doc already names.
59- Never run this outside the devshell; like every other target here it needs
60  the pinned fenix rustc, not whatever `rustc` is on the host `PATH`.