Skip to content

[0.31][Backend] Reduce repeated policy adapters and choose a dispatch strategy #32

Closed

Description

@a19q3

Engineering closure — 2026-09-25

Engineering closure through 0.31 follows the maintainer’s September 25 instruction to skip #30 and exclude 0.32. Release version promotion, fresh distribution bundles, the final release gate and independent release review remain under #30. This closure neither transfers the 0.30 review waiver nor claims an independent security audit or published 0.31 release.

Commit 648bb836, boundary review and acceptance map, isolated attribution, and reproduction/coverage map reconcile the checklist below.

Accepted decisions — 2026-09-21

D3: share helpers only by the complete decoder contract and retain explicit direct per-action stubs. Keep linear dispatch and existing unknown-tag/common-check ordering in the initial 0.31 implementation. Measure through 64 actions after the immediate change. Saved-selector routing, comparison trees, and jump tables are deferred to a follow-up targeting 0.32; no crossover threshold is invented.

Full decision record — baseline: 8d6e200caa58d81429912df925a9322f691cb41d; research baseline remains 6d75a6e7. The choices below were the original open questions; this decision record now governs implementation. The issue stays open until its implementation acceptance passes.

Parent: #30

Target: 0.31. Baseline: branch 0.30, commit 6d75a6e7e4ce55b940697428d895eda74a51ca86. Implementation and engineering acceptance are complete; the accepted decisions define the bounded scope and deferred work.

Problem and evidence

src/codegen/policy.rs emits sequential tag comparisons and one positional adapter for each declared action, including actions with matching parameter shapes.

The existing 32-byte witness corpus reports:

Actions ELF bytes Maximum successful cycles
1 3,984 11,163
2 5,080 11,441
4 7,280 11,999
8 11,680 13,115

These figures include action bodies and validation. The 7,696-byte increase is not all removable dispatch overhead. Baseline: docs/CELLSCRIPT_COST_REGRESSION.md.

Decisions to record

  • Share decoding helpers by exact parameter layout, share complete adapters, or retain per-action adapters? Define the sharing key, including parameter source, width, ABI placement, and any validation that affects behavior.
  • Keep linear dispatch for small policies, use a comparison tree above a threshold, or introduce another bounded form? Measure bytes and worst-case cycles for dense and sparse tags before choosing thresholds.
  • How will the checker bind each admitted tag to its exact decoder and action after sharing? The current checker requires one adapter per variant and specific dispatch shapes: crates/cellscript-artifact-checker/src/checker.rs.
  • Does the chosen representation require a versioned lowering/checker contract? Keep wire tags, envelope rules, action interfaces, and common-check behavior unchanged unless a separate ABI decision is approved.

Acceptance

  • Record separate decisions for decoder sharing and dispatch, including measured rejected alternatives.
  • Compare 1/2/4/8 actions at 8/32/128-byte widths, mixed layouts, sparse tags, and the declared maximum supported action count.
  • Measure every action and report the worst successful path; measure unknown-tag and malformed-argument rejection paths.
  • Demonstrate lower ELF growth for repeated layouts; choose concrete byte/cycle budgets from the experiment before merging.
  • Preserve exact action routing, common-check order, failure propagation, and boundary validation. Add mutations that attempt decoder/action substitution and undeclared control-flow targets.
  • Run ./scripts/cellscript_gate.sh backend for the implementation and ./scripts/cellscript_gate.sh ci before claiming merge readiness. Retain fresh cost reports from the exact tested commit. Preserve accept/reject behavior and independent artifact validation; changes to machine contracts must update producers, checker, lowering records, source maps, and negative mutation tests together.

Coordinate with the entry-frame/copy issue so both changes use one adapter contract. A jump table is an option to evaluate, not an assumed improvement.

Activity

  1. added this to the 0.31 milestone on Sep 21, 2026
  2. a19q3 commented on Sep 21, 2026

    @a19q3
    CollaboratorAuthor

    Research notes, 2026-09-21. CellScript source checked at 6d75a6e7. These are proposed experiments and decisions, not implementation results.

    There are three separate costs to isolate before choosing a new dispatch scheme.

    Additional findings in the current implementation

    1. The supported maximum is 64 artifact variants, while the growth corpus stops at eight. Policy envelopes separately allow at most eight records. These limits describe different dimensions: crates/cellscript-ckb-adapter/src/policy_witness.rs:10.
    2. A policy with common checks scans its tag twice: once to reject undeclared tags before common checks, then again to choose the action after those checks. See src/codegen/policy.rs:52. Removing the first scan outright would change which checks execute for an unknown tag.
    3. Matching byte widths do not establish interchangeable decoders. The wrapper consults type-hash, runtime-bound and bounded-plan parameter indices, parameter sources, dynamic layout, and outgoing argument counts: src/codegen/abi.rs:134.
    4. Large unsigned u32 tags can hit the wide-immediate fallback in [0.31][Backend] Shorten wide immediate materialization #31. Benchmark dispatch again after that improvement before fixing thresholds.

    LLVM's switch lowering considers case count, range and target suitability, and can partition cases into dense groups. That supports measuring density as well as action count; it does not supply a CKB-specific crossover threshold.

    Proposed experiments

    Experiment Keep constant Vary
    Dispatch Action bodies and argument layouts 1/2/4/8/16/32/64 tags; dense, sparse and high-u32 values
    Decoder sharing Action count and tag set One shared layout versus mixed sources/layouts
    Common-check routing Same common checks and failure behavior Existing double lookup versus a saved bounded selector
    Invalid paths Same envelope and selected Script Missing tag, gaps, and values beyond the last tag

    Measure code ranges for dispatch, adapters and action bodies separately, plus total ELF and per-action/worst-path cycles. The existing 1-to-8 growth does not identify how much each component contributes.

    I would prototype shared decoder helpers with direct per-action stubs before sharing whole adapters or introducing indirect jumps. This keeps the final action target explicit, though extra helper calls may cost cycles. For common checks, evaluate a validated selector stored safely across calls; reusing a caller-clobbered register is insufficient. Even a selector design must preserve unknown-tag rejection before common checks and first-failure propagation.

    A jump table adds table bytes, range checks, hole handling, and an independently checkable target set. The current checker binds adapters to exact actions. Do not trade that binding for an unchecked function pointer.

    Decision to record: choose decoder sharing and tag routing independently. Retain linear dispatch for ranges where it wins; make any crossover threshold a result of the matrix above. No new dispatch strategy has been benchmarked yet.

  3. a19q3 commented on Sep 23, 2026

    @a19q3
    CollaboratorAuthor

    Review after the 0.31 implementation, 2026-09-23.

    D3 is implemented at 65df937f: identical complete decoder contracts share a helper behind direct per-action stubs, while linear dispatch and the unknown-tag/common-check order remain. See policy emission, the decoder/adapter contract and the implementation report. Backend and CI passed in the final implementation and validation record.

    Remaining work:

    1. Review the sharing key field by field, including parameter source, flattened ABI, wide values, bounds, runtime requirements and frame layout. Confirm that an uncertain contract gets its own adapter. Equal payload width must never be sufficient to share.
    2. Review the v9 checker bindings from admitted tag to direct stub, decoder and action. Trace the saved return address, decoder return, outgoing arguments and failure path together with [0.31][ABI] Right-size entry frames and evaluate policy argument copy removal #33. Map existing substitution/control-flow mutations to the acceptance checklist and add a regression only if the review exposes a gap.
    3. Publish a short component table from the report's policy_code_bytes / policy_code_ranges: dispatch, decoder, stub and action bytes, with the measured successful/rejected paths. The final 8-action ELF change, 11,680 → 8,048 bytes, is the combined cost pass; the intermediate comparison is needed to attribute decoder sharing itself.
    4. Carry saved-selector routing, comparison trees and jump tables into a separately scoped follow-up targeting 0.32, as already deferred by accepted D1–D6 decisions. Its first task is measurement against this new baseline, not choosing an unmeasured crossover threshold.

    The open work for this child is review and evidence acceptance. Replacing linear dispatch is not a missing 0.31 requirement.

  4. a19q3 commented on Sep 25, 2026

    @a19q3
    CollaboratorAuthor

    Engineering acceptance complete at 648bb836.

    Validation on clean commit 648bb8362a82586821c0d4ae9f8b2250dd56ab11: local dev, full ci, and full backend passed; GitHub CI also passed and retains the fresh cost/shape/strict-audit artifacts. The final clean-source cost pair passes another 6,782-metric comparison against the archived candidate. The full backend gate rebuilt pinned CKB f7fa4436 and passed the local-node 43-action, 17-Lock and 26-stateful-scenario matrix. Local acceptance report SHA-256: 8a1aeb4221409f187e82a342c7335db19432a3fd800e9182e1c65806655a889c. This is local-node evidence, not a new public-network deployment.

    The D3 ablation holds D2/D4/D5 constant and disables sharing only. The native comparison passes 6,782 comparable metrics with no regression. The 8-action/32-byte sample has unchanged dispatch/action code; 2,208 dedicated-adapter bytes become a 252-byte decoder plus 320 bytes of direct stubs. ELF 9,680 → 8,048 bytes; success/rejection cycles 13,588/11,890 → 13,189/11,491. The report contains all 41 rows and their component ranges. Linear dispatch and common-check ordering remain; #37 owns alternate routing.

    Boundary review / acceptance map · attribution and exact report pairs · frozen coverage and reproduction.

    Engineering closure through 0.31 follows the maintainer’s September 25 instruction to skip #30 and exclude 0.32. Release version promotion, fresh distribution bundles, the final release gate and independent release review remain under #30. This closure neither transfers the 0.30 review waiver nor claims an independent security audit or published 0.31 release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions