Skip to content

[0.31][ABI] Right-size entry frames and evaluate policy argument copy removal #33

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

D4: reduce selected policy-adapter private frames using proven parameter/helper requirements. Keep outer WitnessArgs capacity, overlapping normalization, and parent-to-adapter copy ownership unchanged. Unknown requirements use the existing bounded layout. Borrowing, word-copy changes, and staged outer loading are deferred to a follow-up targeting 0.32.

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

The entry wrapper reserves a 5,376-byte base frame, including a 4,096-byte witness buffer and 1,024-byte Script buffer: src/codegen/mod.rs. Small fixed-width parameters use the same base layout.

The policy adapter copies selected arguments byte by byte into a private buffer before decoding: src/codegen/abi.rs. The independent checker explicitly enforces the private frame and copy loop: crates/cellscript-artifact-checker/src/checker.rs.

Frame bytes are reserved address space, not an instruction count. A smaller frame does not imply a proportional cycle saving.

Decisions to record

  • Which buffer bounds can be derived from the selected entry, its parameter layout, and its admitted runtime operations? What bounded fallback remains for dynamic cases?
  • Does reducing a buffer accidentally reject an input previously admitted by the same ABI? Distinguish semantic payload bounds from loader capacity and preserve the accepted input set.
  • Retain a private copy, use a faster bounded copy, or decode through a borrowed read-only span? Start with frame sizing; admit borrowing only after documenting parent-buffer lifetime, nested calls, aliasing, and clobber rules.
  • How should these ownership and frame rules be represented and independently checked? Decide schema compatibility/versioning before changing current machine patterns.

Acceptance

  • Record frame-sizing and copy-ownership decisions separately.
  • Demonstrate a smaller safe frame for selected small/payload-free entries; retain a tested fallback where static sizing is insufficient.
  • Test empty/exact-bound/over-bound/truncated inputs, fixed bytes wider than eight bytes, Script args, nested helper calls, and outgoing arguments that use the stack.
  • Prove saved return addresses, caller-owned buffers, and outgoing argument areas remain disjoint; add negative artifact mutations for the new contracts.
  • Report ELF, successful and rejection cycles, per-entry frame sizes, and a separately named call-chain stack metric. Do not relabel the old maximum-individual-frame metric as a dynamic peak.
  • Preserve witness bytes and wire ABI for the initial implementation; any protocol change needs its own explicit decision.
  • 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 policy adapter sharing. Keep buffer layout in frame/ABI owners and keep the checker independent of compiler implementation code.

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.

    The policy path has two distinct copy operations, and they have different safety conditions.

    Copy inventory

    • src/codegen/abi.rs:545 validates all three WitnessArgs fields, then moves the input_type payload to the beginning of the same buffer. Source and destination may overlap; the current forward copy moves bytes toward the lower address.
    • src/codegen/abi.rs:514 copies the selected policy record's CSARG bytes into the adapter's private buffer. This is the parent-to-callee copy identified in the issue.

    A faster copy must handle the correct overlap direction and exact tail bounds. Replacing byte loads with word loads also needs an explicit alignment/access contract; reading beyond the validated span is not acceptable.

    Using the pinned CKB-VM instruction cost model, the adapter's present seven-instruction loop has a static instruction-cost sum of 15n + 3 for n copied bytes, excluding setup and surrounding code. That gives 603 for 40 bytes and 2,043 for 136 bytes. These are analytic loop estimates, not measured transaction savings. The earlier WitnessArgs move is a separate loop and copies a different span.

    Why parameter width alone cannot size the outer buffer

    The normalizer permits optional lock and output_type fields and checks their framing too. A small input_type does not imply a small complete WitnessArgs. Likewise, one selected policy record does not bound the other records in its envelope.

    The CKB syscall RFC's partial-loading contract distinguishes bytes copied from the available length reported through the length pointer. A smaller buffer therefore requires a deliberate loading/validation strategy, not just a smaller allocation constant. A staged reader could retain the admitted input set, but additional syscalls and validation have their own cost.

    Suggested implementation order

    1. Start with the adapter frame, where selected arguments have already been isolated and their length is explicit. Evaluate omitting Script storage when no parameter reads Script args. Inventory all fixed-offset users first.
    2. Measure a smaller private-copy frame before trying borrowed spans. Keep a conservative fallback for dynamic layouts.
    3. Evaluate borrowing only after proving the parent remains live and the full downstream call graph cannot mutate, outlive, or overwrite the span. Shared decoders in [0.31][Backend] Reduce repeated policy adapters and choose a dispatch strategy #32 must agree on this ownership rule.
    4. Treat removal of the outer normalization copy as a separate experiment. It changes where subsequent parsing reads and may affect more checker patterns than adapter sizing.

    The RISC-V ABI requires 16-byte stack alignment for the standard ABI and does not preserve storage below sp. Preserve CellScript's outgoing-argument isolation as well.

    Decisions to record: separate outer loading capacity, selected-argument storage and borrowing. A smaller private adapter is the suggested first candidate; zero-copy remains conditional on ownership and checker evidence. Include large optional WitnessArgs fields, first/last selected records, nested calls and malformed final fields in acceptance tests.

  3. a19q3 commented on Sep 23, 2026

    @a19q3
    CollaboratorAuthor

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

    D4's private-frame sizing is implemented at 65df937f. The outer 4,096-byte WitnessArgs capacity, overlapping normalization move, and parent-to-adapter copy remain intentional. Unknown requirements retain the bounded fallback. See the adapter implementation, accepted D1–D6 decisions, and the final implementation and validation record.

    Remaining work:

    1. Review the proof used to select the compact private capacity, including any helper or Script-argument consumer that forces fallback. Check alignment, saved return addresses, caller-owned buffers and outgoing argument areas against the shared decoder contract in [0.31][Backend] Reduce repeated policy adapters and choose a dispatch strategy #32.
    2. Attach an acceptance map for empty/exact/truncated/over-bound payloads, wide fixed bytes, large optional WitnessArgs fields, first/last policy records, Script args, nested helpers and more than eight flattened arguments. These cases must preserve the admitted witness set; a smaller selected payload does not justify shrinking the outer loader.
    3. Publish private-frame and whole-call-chain measurements separately. The 32-byte policy sample's static call-chain bound falls from 11,472 to 6,160 bytes in the combined pass. That value is a static bound, not an observed high-water mark or a proportional cycle saving.
    4. Give borrowing, copy-loop changes and staged loading their own follow-up with explicit lifetime, aliasing, alignment, tail-access and syscall-cost decisions. They are deferred experiments, not unfinished parts of D4.

    Close this child after the exact frame/ownership contract and its evidence receive the required boundary review. The remaining copy operations alone are not a reason to keep the accepted 0.31 implementation open.

  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 engineering review maps fixed-capacity derivation, fallback, private-copy ownership, saved ra and temporary outgoing arguments to source, VM cases and rebound artifact mutations. A new regression combines a shared decoder, wide/stack arguments and a saved-ra mutation into the payload region; the independent checker rejects it. In the fixed 32-byte policy sample, private adapters reserve 64 bytes, the parent reserves 4,192, and the static call-chain bound is 6,160. The outer 4,096-byte input boundary and private copy remain. These are static bounds, not observed peaks; #38 owns borrowing/copy changes.

    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