Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions docs/adr/0001-kernel-adapter-boundary.md

Large diffs are not rendered by default.

152 changes: 76 additions & 76 deletions docs/design/native-feature-parity.md

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions docs/native-refactor-progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ them for the record only. "Linux aarch64" is the maintainers' Cortex-A725 host.

| Item | What it is | Measured size | Deferred by | Prototype and evidence | Reopens when |
| --- | --- | --- | --- | --- | --- |
| D2-B, the quiet-bar protocol and one receipt read per bar | PERF-D2's lane: a per-bar bar-open request, a bulk fold of quiet driver points, the adapter's quiet-open predicate with lazy open bookkeeping, and a gate before the receipt poll. D2-A landed the hook declarations and the O(1) `has_command_after` (`src/native_execution_consumer.hpp:321`). PERF-L4 gated the read inside `observe_terminal_receipts` (`src/source/pine_adapter.cpp:6446`), which still runs about three times a bar | PERF-D2, Linux aarch64 (Cortex-A725), callgrind and GBench: about ×0.88 on the 14 probes (an estimate from a measured −13 to −14 % floor bound for the bar-open request alone); after PB the receipt poll still costs about 250 instructions a bar on the no-order floor. On this tree the quiet-bar probe asks the receipt gate 40,996 times and each once-per-bar-open gate 13,248 times: 3.09 reads a bar, 82 % of them stopped at the gate (macOS arm64, AppleClang 17, Release) | "R5 PERF-D2 DONE 2026-09-24 19:56" (D2-B after V19-D); "R5 SUPERVISOR RULING 2026-09-25 00:15 Taipei": "D2-B (quiet-bar protocol) waits for PERF-D3's verdict"; TRIAGE1 §D | An unpublished prototype branch and its patches. PERF-D3's S1 (next row) prototyped the request and the lazy opening | A post-release performance epoch. Done when the receipt gate is asked as often as the once-per-bar gates |
| D2-B, the quiet-bar protocol and one receipt read per bar | PERF-D2's lane: a per-bar bar-open request, a bulk fold of quiet driver points, the adapter's quiet-open predicate with lazy open bookkeeping, and a gate before the receipt poll. D2-A landed the hook declarations and the O(1) `has_command_after` (`src/native_execution_consumer.hpp:321`). PERF-L4 gated the read inside `observe_terminal_receipts` (`src/source/pine_adapter.cpp:6585`), which still runs about three times a bar | PERF-D2, Linux aarch64 (Cortex-A725), callgrind and GBench: about ×0.88 on the 14 probes (an estimate from a measured −13 to −14 % floor bound for the bar-open request alone); after PB the receipt poll still costs about 250 instructions a bar on the no-order floor. On this tree the quiet-bar probe asks the receipt gate 40,996 times and each once-per-bar-open gate 13,248 times: 3.09 reads a bar, 82 % of them stopped at the gate (macOS arm64, AppleClang 17, Release) | "R5 PERF-D2 DONE 2026-09-24 19:56" (D2-B after V19-D); "R5 SUPERVISOR RULING 2026-09-25 00:15 Taipei": "D2-B (quiet-bar protocol) waits for PERF-D3's verdict"; TRIAGE1 §D | An unpublished prototype branch and its patches. PERF-D3's S1 (next row) prototyped the request and the lazy opening | A post-release performance epoch. Done when the receipt gate is asked as often as the once-per-bar gates |
| S1a, kernel quiet-bar driving (PERF-D3) | A bar with nothing live, a flat book and a declined opening is driven in a quiet form: the kernel writes the bookkeeping its four price points would leave (ordinals, the last two driver marks, the floor, the point epochs) without walking them or calling the bar-open callback. Additive host cadence declarations; C hosts declare from their callback table; one C symbol under the PF_API checklist | PERF-D3, Linux aarch64 (Cortex-A725), GBench time and `perf stat` instructions, on the L3 tree `b33ea659`, S1a with S1b: no-order floor 0.764 / 0.660 in time (0.718 / 0.656 in instructions), 14 probes 0.958 / 0.921 (0.892 / 0.856), 100 slots 0.972 / 0.956 per slot. A bare kernel host that opts in: idle 0.545× (35 % fewer instructions a bar), market 0.81×, bracket 0.92×. Byte-identical: corpus 312/312, the 132-configuration battery with its hash columns | "FINDING PERF-D3 (filed for a post-release epoch per the owner's cap)" (ledger, 2026-09-24 16:58 UTC); "OWNER DECISION 2026-09-25 00:20 Taipei"; TRIAGE1 §D: "PERF-D3 S1/S2 (post-release epoch)" | An unpublished prototype branch (five commits on L3's tree) and its patches | The post-release epoch, with the owner calls PERF-D3 lists (the API under the existing ruling, the restated gate count) |
| S1b, the Pine lazy opening | The Pine host records what its skipped opening and its uncalled input callback would have written, at the bar's first host callback. Member functions only, no layout change | Measured with S1a. A quiet bar then asks 15 gates, not 30, so the quiet-bar cost check `CHECK(asked >= 20 * o.boundary.size())` (`tests/test_adapter_quiet_bar.cpp:1054`) must be restated | As S1a | As S1a. The in-script hash witnesses caught the prototype's one miss (`awaiting_legacy_script_open_ms_`) | As S1a |
| S1c, the held-position wake band | S1 for bars that hold a position with nothing live, 41 % of the median public slot's bars. The host declares a price band, and the kernel calls the opening only when the bar's range leaves it. The adapter derives the band from its margin model and declines when it cannot bound it | PERF-D3, a measurement-only upper bound without the band, which is not sound in general: 100 slots 0.776 / 0.716 per slot, 14 probes 0.830 / 0.761, cumulative against L3 with S1, S2 and D2-A | As S1a; the owner call "Fund the price band" | An unpublished measurement-only script | As S1a. Size L, parity risk high: TradingView's margin money rules |
Expand All @@ -183,7 +183,7 @@ them for the record only. "Linux aarch64" is the maintainers' Cortex-A725 host.
| Compact journal records and a history ring buffer (L3) | Smaller command records and a ring buffer for the request core's history. `WorkingRequestCore::history` (`include/pineforge/native_order.hpp:1709`) hands out the whole vector and the core holds `history_` (`include/pineforge/native_order.hpp:2280`) inside `native_order_v7`, so both are a layout and API change | L3, Linux aarch64 (Cortex-A725), `perf` call graphs, inclusive shares: at most 3.68 % of bracket, 11.99 % of re-issue, 4.26 % of market and 3.42 % of the 14 probes (retiring the history alone: 1.64, 4.51, 2.53 and 1.00 %); only part is realisable, since records must still be written | "R5 L3 DONE + ACCEPTED 2026-09-24 22:10 Taipei", ruling 1: "DEFERRED to the PERF-D3 structural plan"; TRIAGE1 §D: "compact journal records" | Lane L3's code, merged by INT22 (#285); its profiles are unpublished | A `native_order` layout epoch |
| A checked-values handoff for executions (L3) | `execution_values` runs twice, in `check_execution` (`src/native_order.cpp:4887`) and again in `apply_execution` (`src/native_order.cpp:4910`); handing the checked values across needs a public type, an API addition with no layout change | L3, as above: 2.21 % of bracket for the two together, about 1.1 % recoverable | L3 ruling 2: "a public checked-values handoff for executions (~1.1% bracket) -> DEFERRED" | As above | An owner decision on the API addition |
| A chunked `trades_` store (L3) | Closed trades in chunks rather than one vector; `trades_` (`include/pineforge/engine.hpp:546`) is protected `BacktestEngine` state, so script ABI | L3, as above: on bracket, vector growth 0.64 %, `Trade` moves 0.74 % and `record_close_trade` 0.43 % (at most about 1.4 %); at most 0.35 % on the 14 probes | L3 ruling 4: "chunked trades_ store (ABI) -> DEFERRED" | As above | An `engine_script_run` epoch that changes the engine's protected layout |
| A zoned chart-day memo (D2-D finding 4) | On a chart with a timezone, `libc_chart_day_key` (`src/source/pine_adapter.cpp:17351`) takes the process timezone lock through `ScopedTimezone` and calls `localtime_r` on every bar, because `on_bar_open` (`src/source/pine_adapter.cpp:22250`) reads `chart_day_key` (`src/source/pine_adapter.cpp:22487`) unconditionally. The non-UTC monthly Sharpe/Sortino walk, `month_key_local` (`src/engine_metrics.cpp:33`), pays one `localtime_r` per equity point. UTC charts use D2-D's memo, `civil_chart_day_key` (`src/source/pine_adapter.cpp:17335`) | Not measured on a zoned chart. The UTC path cost 134 instructions a bar before D2-D's memo and 68 after (D2-D, Linux aarch64, callgrind on the floor). PERF-K1: `ScopedTimezone` copies the zone name, which allocates for names of 16 or more characters under libstdc++ | TRIAGE1 §D: "D2-D finding 4 (the timezone `localtime_r` memo)", under the owner's cap. D2-D's report: an exact zoned memo "would need the zone's transition table, which belongs in `timezone.cpp`" | None; the UTC memo's witness is `tests/test_chart_day_memo.cpp` | A lane that owns `src/timezone.cpp` and builds an exact zone-transition memo (PERF-K1's Sitka case shows why a shortcut is not exact), byte-identical, with the zoned keys of `test_chart_day_memo` still equal to the computed ones |
| A zoned chart-day memo (D2-D finding 4) | On a chart with a timezone, `libc_chart_day_key` (`src/source/pine_adapter.cpp:17486`) takes the process timezone lock through `ScopedTimezone` and calls `localtime_r` on every bar, because `on_bar_open` (`src/source/pine_adapter.cpp:22387`) reads `chart_day_key` (`src/source/pine_adapter.cpp:22624`) unconditionally. The non-UTC monthly Sharpe/Sortino walk, `month_key_local` (`src/engine_metrics.cpp:33`), pays one `localtime_r` per equity point. UTC charts use D2-D's memo, `civil_chart_day_key` (`src/source/pine_adapter.cpp:17470`) | Not measured on a zoned chart. The UTC path cost 134 instructions a bar before D2-D's memo and 68 after (D2-D, Linux aarch64, callgrind on the floor). PERF-K1: `ScopedTimezone` copies the zone name, which allocates for names of 16 or more characters under libstdc++ | TRIAGE1 §D: "D2-D finding 4 (the timezone `localtime_r` memo)", under the owner's cap. D2-D's report: an exact zoned memo "would need the zone's transition table, which belongs in `timezone.cpp`" | None; the UTC memo's witness is `tests/test_chart_day_memo.cpp` | A lane that owns `src/timezone.cpp` and builds an exact zone-transition memo (PERF-K1's Sitka case shows why a shortcut is not exact), byte-identical, with the zoned keys of `test_chart_day_memo` still equal to the computed ones |
| Thread-local reads that remain per call | D2-C moved the three scopes' writes onto the pump's block, `pump_ambient` (`src/native_execution_consumer.hpp:496`). A reader still reaches the block through `tl_runtime_ambient` (`src/ta_extremes_volume.cpp:29`): `bar_context` (`src/ta_extremes_volume.cpp:82`), read by every `ExtremeRing::update` (`src/ta_extremes_volume.cpp:117`), which backs `ta.highest`, `ta.lowest`, `ta.highestbars` and `ta.lowestbars` and, through an embedded `Highest` and `Lowest`, `ta.stoch`, `ta.wpr` and `ta.range`; and `matching_day_partition` (`src/timeframe.cpp:628`), read by the default-anchored `ta.vwap` through `session_day_index`, by `crosses_boundary` on a DAY period (`timeframe.change("D")`) and by the session-period helpers. `decompose_ms_local` (`src/session_time.cpp:250`) reads two thread-locals per call on a zoned clock, and the prepared order storage's `free_blocks` (`src/native_order.cpp:620`) one per prepared mutation. `ema_na_warmup_flag` is read once per EMA, at its first compute | One thread-local access per reader call. AUDIT4, macOS arm64, a dlopen'd tutorial-MACD probe, counted by `_tlv_get_addr` breakpoints and by descriptor patching: 1.000 a bar for one `ta::Highest` and for one default `ta::VWAP`, 1.999 for one `pine_hour` on a New York clock, 0.000 on the no-order floor and with orders. On Linux a TLS-descriptor call costs about 20 instructions: D2-C's 13 → 4 calls a bar was 260 → 80 instructions (Linux aarch64, GCC 13, callgrind). INT23: 2.00 a bar on the zoned probe 064, 0.08 on probe 008 | None for the TA and day-partition readers: "RULING: INT23 ACCEPTED" (ledger, 2026-09-24 23:30 UTC) reads "dlopen TLS 13->0/bar", and INT23's report names only the timezone and order-core residuals. This row is their record | Unpublished (AUDIT4's scratch) | A byte-identical lane that hands these readers the pump's block without a thread-local read. `ta::bar_context()` is script ABI, so its public declaration stays as it is |
| "audit-off mode", "LTO", "data-oriented layout" | Named in "OWNER DECISION 2026-09-25 00:20 Taipei" among the post-release structural items: "S1 bulk quiet bars, S2 per-strategy specialization, audit-off mode, LTO+PGO, data-oriented layout". No engine or campaign document defines, designs or measures the three | None | The owner decision | None | Struck from this inventory until a design note defines one of them |

Expand All @@ -199,7 +199,7 @@ unless the retention is Full, and `presize_logs`
(`src/native_execution_consumer.cpp:10278-10289`) sizes no journal under Window.
`NativeRunSpec::event_retention` (`include/pineforge/native_run_spec.hpp:714`)
defaults to Window, and the Pine adapter declares
`NativeEventRetention::Window` (`src/source/pine_adapter.cpp:2996`). K4 still
`NativeEventRetention::Window` (`src/source/pine_adapter.cpp:3001`). K4 still
sizes Full runs (driver log and journal) and Commands runs (journal), and its
witness, `tests/test_native_batch_log_presize.cpp`, runs under Full. PERF-K24's
replay figure (14.9 % less CPU on Linux aarch64) describes the tree before V19-B. In
Expand All @@ -210,7 +210,7 @@ or more, and the same run under Full holds 66,000 points in one block of
**PERF-L4 reads the receipts about three times a bar, not once.** PERF-L4's
brief asked it to "read the receipts ONCE per bar instead of three times". The
lane gated the read instead: `observe_terminal_receipts`
(`src/source/pine_adapter.cpp:6446`) returns at its receipt gate when no
(`src/source/pine_adapter.cpp:6585`) returns at its receipt gate when no
command lies above its cursor. `every_gate_is_taken_both_ways`
(`tests/test_adapter_quiet_bar.cpp:1061`) prints
`gate ReceiptRead asked 40996 quiet 33552 live 7444`, against `asked 13248`
Expand Down Expand Up @@ -241,7 +241,7 @@ Mac's 489 and 412 and 1003 of 1249, and no Mac output moved.

- **PERF-L4's retired reader slots.** `event_high_water_reader_` and
`terminal_receipt_high_water_reader_`
(`include/pineforge/source/pine_adapter.hpp:2388-2389`), with their type
(`include/pineforge/source/pine_adapter.hpp:2395-2396`), with their type
`ReceiptHighWaterReader`, are storage nothing reads or writes since PERF-L4:
they keep `PineExecutionAdapter`'s layout, which is part of
`PineStrategyHost`'s and so of the generated script's ABI. They go at the next
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/native-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -1496,7 +1496,7 @@ adapter answers `margin_check_allowed` with TradingView's scheduling — which
includes the post-exit re-size: when a priced bracket leg of the script bar
fills, the slice resting at that bar's adverse extreme was sized on the
pre-exit book, and the legacy broker cancelled and re-scheduled it there
(`margin_check_allowed` `pine_adapter.cpp:17830-17863`), so the adapter admits
(`margin_check_allowed` `pine_adapter.cpp:17965-17998`), so the adapter admits
the kernel's own point for that driver point while (and only while) a slice
rests —
`resolve_margin_requirement` with its ten-significant-digit money and
Expand Down
23 changes: 23 additions & 0 deletions include/pineforge/source/pine_adapter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2010,6 +2010,13 @@ class PineExecutionAdapter {
NativeStrategyHost& require_host() const;
bool modeled_input() const;
native_order::CohortHandle cohort_for(const SourceId& id);
// held_cohort_ids_ upkeep: hold_cohort after `id`'s cohort gains an
// opening or live units, forget_emptied_cohorts after any cohort loses
// its last of both.
void hold_cohort(const SourceId& id);
void forget_emptied_cohorts();
const std::vector<SourceId>& held_cohort_ids() const noexcept;
bool held_cohort_ids_exact() const noexcept;
std::optional<native_order::RequestHandle> submit_or_replace(
native_order::Request request, PlacementSnapshot snapshot, bool opening,
const SourceId& replacement_key = {});
Expand Down Expand Up @@ -2517,6 +2524,14 @@ class PineExecutionAdapter {
std::uint32_t entry_attempts_on_bar_ = 0;
std::unordered_map<SourceId, CohortFacts> cohorts_by_id_;
std::vector<SourceId> cohort_order_;
// The ids whose cohort holds an opening or live units, in cohort_order_
// order, which is the order of their kernel handles: cohort_for opens the
// handle as it appends the id, and the core numbers handles upward.
// cohorts_by_id_ keeps a cohort for every id the run has used, so a walk
// that reads only `opened` and `live_units_by_origin` walks this instead
// and costs what is open, not every id used so far. Not state: a function
// of cohorts_by_id_ and cohort_order_.
std::vector<SourceId> held_cohort_ids_;
PlacementTable placement_;
std::unordered_map<std::uint64_t, native_order::RequestHandle> live_by_source_key_;
std::unordered_map<std::uint64_t, BracketRoster> bracket_families_;
Expand Down Expand Up @@ -2845,6 +2860,14 @@ class PineExecutionAdapter {
std::vector<std::pair<native_order::CohortHandle, std::uint64_t>> released_origins;
native_order::RequestHandle lot_probe{};
bool roster_pass_due = true;
// A memo too (a run begin empties it): the kernel rosters that hold
// members, by handle, as of the last pass, and how many of the core's
// cohort receipts it has read. Only an applied add puts a member in a
// roster, so a pass reads these (held_rosters, its working list), not
// every roster the run opened.
std::vector<native_order::CohortHandle> rostered;
std::size_t cohort_receipts_read = 0;
std::vector<const native_order::CohortRoster*> held_rosters;
void clear() noexcept {
roots.clear();
askable_origins.clear();
Expand Down
Loading