Skip to content

feat: add field-inline tracing support - #1596

Merged
0xAndoroid merged 6 commits into
mainfrom
prover-stack/01-field-inline-tracing
Jun 17, 2026
Merged

feat: add field-inline tracing support#1596
0xAndoroid merged 6 commits into
mainfrom
prover-stack/01-field-inline-tracing

Conversation

@markosg04

Copy link
Copy Markdown
Collaborator

Part of the draft Jolt prover stack.

Base: main
Head: prover-stack/01-field-inline-tracing

Adds field-inline guest/tracer/program wiring and the SDK feature hooks needed by later prover work.

Validation before submission:

  • cargo fmt -q -- --check
  • cargo metadata --no-deps --format-version 1
  • local gh stack push simulation against a temporary bare remote
  • forbidden-path scan for handoffs/, old STACK.md, stack/, and old stack workflow

@github-actions github-actions Bot added the no-spec PR has no spec file label Jun 9, 2026
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Warning

This PR has more than 500 changed lines and does not include a spec.

Large features and architectural changes benefit from a spec-driven workflow.
See CONTRIBUTING.md for details on how to create a spec.

If this PR is a bug fix, refactor, or doesn't warrant a spec, feel free to ignore this message.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Benchmark comparison (crates)

group                           main_run                               pr_run
-----                           --------                               ------
EqPolynomial::evals/17          1.00  1905.5±35.93µs        ? ?/sec    1.10      2.1±0.02ms        ? ?/sec
EqPolynomial::evals/19          1.00      7.1±0.06ms        ? ?/sec    1.11      7.9±0.06ms        ? ?/sec
EqPolynomial::evals/20          1.00     15.1±0.10ms        ? ?/sec    1.06     16.1±0.07ms        ? ?/sec
EqPolynomial::evals/22          1.00     57.4±0.28ms        ? ?/sec    1.11     63.5±2.22ms        ? ?/sec
EqPolynomial::evaluations/14    1.22   784.6±21.44µs        ? ?/sec    1.00   641.0±16.80µs        ? ?/sec
EqPolynomial::evaluations/18    1.00      6.4±0.06ms        ? ?/sec    1.14      7.3±0.10ms        ? ?/sec
EqPolynomial::evaluations/20    1.00     28.1±0.19ms        ? ?/sec    1.06     29.8±0.21ms        ? ?/sec
Fr::from_bytes                  1.00    156.3±0.63ns        ? ?/sec    1.05    164.8±1.42ns        ? ?/sec
Fr::mul_u128                    1.00     22.5±0.27ns        ? ?/sec    1.09     24.6±0.18ns        ? ?/sec
Fr::mul_u64                     1.00     11.5±0.09ns        ? ?/sec    1.07     12.3±0.12ns        ? ?/sec
Fr::to_bytes                    1.00     17.6±0.24ns        ? ?/sec    1.14     20.0±0.25ns        ? ?/sec
Polynomial::bind/14             1.00    131.2±5.45µs        ? ?/sec    1.18    155.0±5.66µs        ? ?/sec
Polynomial::bind/18             1.00  1911.8±22.36µs        ? ?/sec    1.13      2.2±0.03ms        ? ?/sec
Polynomial::bind/20             1.00      7.7±0.06ms        ? ?/sec    1.10      8.5±0.06ms        ? ?/sec
Polynomial::evaluate/20         1.00     40.3±0.23ms        ? ?/sec    1.10     44.4±0.39ms        ? ?/sec
append_bytes/Blake2b/256B       1.00    330.8±8.92ns        ? ?/sec    1.05    347.7±3.13ns        ? ?/sec
append_bytes/Keccak/256B        1.00    835.1±5.88ns        ? ?/sec    1.11   930.0±19.66ns        ? ?/sec
append_bytes/Poseidon/32B       1.00     92.3±1.35µs        ? ?/sec    1.06     97.4±0.43µs        ? ?/sec
challenge/Blake2b               1.00    649.0±7.96ns        ? ?/sec    1.12   726.2±12.32ns        ? ?/sec
challenge/Keccak                1.00    395.1±3.15ns        ? ?/sec    1.11    439.1±3.34ns        ? ?/sec
challenge/Poseidon              1.00     46.2±0.80µs        ? ?/sec    1.06     48.9±0.30µs        ? ?/sec
g1_add                          1.00    414.2±4.35ns        ? ?/sec    1.12    464.7±2.89ns        ? ?/sec
g1_deserialize_bincode          1.00      9.1±0.06µs        ? ?/sec    1.06      9.7±0.10µs        ? ?/sec
g1_double                       1.00    212.9±2.50ns        ? ?/sec    1.10    233.7±3.22ns        ? ?/sec
g1_msm/1024                     1.00     12.7±0.19ms        ? ?/sec    1.10     14.0±0.03ms        ? ?/sec
g1_msm/16                       1.00    534.6±6.60µs        ? ?/sec    1.12    596.6±3.07µs        ? ?/sec
g1_msm/256                      1.00      4.3±0.01ms        ? ?/sec    1.11      4.7±0.03ms        ? ?/sec
g1_msm/4                        1.00    254.3±2.04µs        ? ?/sec    1.12    283.9±1.59µs        ? ?/sec
g1_scalar_mul                   1.00     65.0±1.19µs        ? ?/sec    1.11     72.3±1.73µs        ? ?/sec
g1_serialize_bincode            1.00    112.1±1.69ns        ? ?/sec    1.07    119.7±1.60ns        ? ?/sec
g2_msm/256                      1.00     15.5±0.03ms        ? ?/sec    1.14     17.7±0.90ms        ? ?/sec
g2_msm/4                        1.00    778.4±5.91µs        ? ?/sec    1.12    868.1±7.18µs        ? ?/sec
g2_msm/64                       1.00      5.6±0.02ms        ? ?/sec    1.13      6.3±0.01ms        ? ?/sec
g2_scalar_mul                   1.00    320.9±2.61µs        ? ?/sec    1.13    361.6±2.28µs        ? ?/sec
gt_scalar_mul                   1.15    893.5±6.81µs        ? ?/sec    1.00    774.4±3.70µs        ? ?/sec
multi_pairing/16                1.00      5.8±0.02ms        ? ?/sec    1.09      6.4±0.02ms        ? ?/sec
multi_pairing/2                 1.15  1249.5±17.58µs        ? ?/sec    1.00  1084.6±71.76µs        ? ?/sec
multi_pairing/4                 1.00  1845.3±18.78µs        ? ?/sec    1.07  1977.0±19.69µs        ? ?/sec
multi_pairing/8                 1.00      3.2±0.01ms        ? ?/sec    1.10      3.5±0.03ms        ? ?/sec
pairing                         1.15    921.7±5.50µs        ? ?/sec    1.00   804.3±16.08µs        ? ?/sec
pedersen_commit/1024            1.00     12.8±0.04ms        ? ?/sec    1.10     14.1±0.08ms        ? ?/sec
pedersen_commit/16              1.00    603.0±3.99µs        ? ?/sec    1.12    672.7±2.83µs        ? ?/sec
pedersen_commit/256             1.00      4.3±0.02ms        ? ?/sec    1.10      4.8±0.01ms        ? ?/sec
pedersen_commit/4               1.00    318.7±3.83µs        ? ?/sec    1.12    356.5±3.40µs        ? ?/sec

@0xAndoroid 0xAndoroid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This slice doesn't build: the #[cfg] attributes added to jolt-riscv's instruction-list macros break the un-updated impl_inline_instruction! callback in jolt-program, the removed JoltTraceRow re-exports break tracer's trace_row.rs, and the SDK macro calls a JoltSharedPreprocessing::new signature that doesn't exist in this slice's jolt-core (details inline — this accounts for every red build job, plus machete; taplo also wants crates/jolt-lookup-tables/Cargo.toml, jolt-core/Cargo.toml, and tracer/Cargo.toml reformatted).

On the soundness side: I diffed all ~45 ported prefix prefix_mle/update_prefix_checkpoint implementations against the jolt-core originals — zero semantic divergences — and required_prefixes() matches every table's combine() usage. The remaining comments are mostly trace/preprocess strictness mismatches and witness-format ambiguities worth settling before the prover PRs consume these artifacts.

Comment thread crates/jolt-riscv/src/lib.rs
Comment thread crates/jolt-riscv/src/lib.rs
Comment thread crates/jolt-riscv/src/lib.rs
Comment thread jolt-sdk/macros/src/lib.rs Outdated
Comment thread crates/jolt-program/src/image/decode.rs Outdated
Comment thread crates/jolt-program/src/execution/trace.rs Outdated
UnsupportedXlen { xlen: usize },
}

pub fn instruction_lookup_index<const XLEN: usize>(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither instruction_lookup_index nor JoltLookupQuery has a production caller in this PR, and the per-call validate_xlen Result diverges from jolt-core's infallible LookupQuery::to_lookup_index pattern on what will be a hot path. If this is staging for the next PR, consider landing it with its consumer; either way the XLEN guard can be a const assertion instead of a runtime check.

Comment thread crates/jolt-lookup-tables/src/tables/prefixes/and.rs Outdated
cpu: &mut Cpu,
) -> FieldInlineTraceData {
let rd_register = operands.rd.unwrap_or(0);
let value = u64::try_from(operands.imm)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A negative imm silently becomes zero here, while the preprocess-side encoded_immediate rejects it with InvalidImmediate. Unreachable today (both decoders zero-extend a 12-bit field), but the two sides should agree on the failure mode before a non-decode construction path appears.


impl From<super::SourceInstructionRow> for $name {
fn from(row: super::SourceInstructionRow) -> Self {
let mut operands = FormatFieldInline::from(row.operands);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sets operands.op on the SourceInstructionRow path, but the From<JoltInstructionRow> conversion generated by impl_final_jolt_row_data!(@from_row ...) leaves op: None. Execution still works (the macro passes $op directly), but x_register_role() returns None, so x-register state capture silently disappears for LoadFromX/StoreToX if instructions are ever materialized from final bytecode rows. Currently unreachable for field ops, but worth setting op in both conversions to kill the asymmetry.

@0xAndoroid 0xAndoroid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few design-direction notes on the lookup-tables port, follow-up to the earlier review.

Comment thread crates/jolt-lookup-tables/src/tables/mod.rs Outdated
j: usize,
suffix_len: usize,
) -> PrefixCheckpoint<F> {
let _ = (checkpoints, r_x, r_y, j, suffix_len);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forward-looking note: I want to redesign the prefix-suffix mechanism to materialize the entire small 2^16 prefix table up front, which would eliminate update_prefix_checkpoint entirely and shrink this code surface. Not blocking for this port, but worth keeping the checkpoint machinery thin — it may not survive the redesign.

Comment thread crates/jolt-program/src/field_inline.rs Outdated
#[cfg_attr(
feature = "serialization",
derive(
CanonicalSerialize,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we still deriving CanonicalSerialize/CanonicalDeserialize here — I thought we were moving off ark serialization in the new stack? It does match the existing serialization-feature pattern in jolt-riscv/jolt-program (kind.rs, row.rs, preprocess/*), so it's consistent with what's landed; flagging the direction question.

@0xAndoroid
0xAndoroid force-pushed the prover-stack/01-field-inline-tracing branch 2 times, most recently from 4e43270 to 393e9cb Compare June 12, 2026 20:02
@0xAndoroid
0xAndoroid self-requested a review June 15, 2026 20:23
@0xAndoroid
0xAndoroid marked this pull request as ready for review June 15, 2026 20:25
@0xAndoroid
0xAndoroid force-pushed the prover-stack/01-field-inline-tracing branch from 18ed880 to 9fba45a Compare June 15, 2026 21:52
@0xAndoroid

0xAndoroid commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Stack maintenance#1596 is the base of the prover stack and targets main directly.

Pika validates it against the latest main and fixes CI as needed; it stays as the foundation the rest of the stack builds on.

📚 Stack A: #1596 (this PR)#1597#1598#1599#1600#1601#1602#1603
📚 Stack B: #1596 (this PR)#1597#1604#1605#1606

#1596 and #1597 are shared by both stacks; each PR targets the one before it.

markosg04 and others added 6 commits June 17, 2026 00:29
- impl_inline_instruction: accept cfg attrs from for_each_instruction_kind
- jolt-riscv: restore trace_row module/exports, from_bits, thiserror dep
  (accidentally dropped while rebasing onto JoltTraceRow foundation)
- jolt-riscv: drop dead test-utils feature and unused rand dep (machete)
- jolt-sdk/macros: restore committed-bytecode preprocessing from main,
  re-apply field-inline hook additively
- tracer/cpu: restore interrupt_enabled match from main
- guests: declare field-inline feature (silences unexpected_cfgs)
- jolt-lookup-tables prefixes: lint-clean ported prefix_mle code
  (unwrap_or defaults, let-else, redundant else, unreachable!)
- taplo fmt
Remove prefix_mle and update_prefix_checkpoint from SparseDensePrefix,
the Prefixes dispatch, and all 40 prefix implementations, along with the
Option-based PrefixCheckpoint alias. Prefixes are now computed solely via
per-phase materialization: evaluate() at binary points builds a dense
table at phase start, the table is bound during the phase's sumcheck
rounds, and the fully bound value becomes the next phase's checkpoint.

Rewrite prefix_suffix_test to exercise that flow end-to-end: materialize,
bind HighToLow with random challenges, check combine(prefixes, suffixes)
against the table MLE every round at c in {0,2}, chain checkpoints from
bound values, and compare the final claim against evaluate_mle over the
full 128-variable point, for both RPP=16 and RPP=8 prover configs.
- Decode FIELD_ASSERT_EQ with rd:None to match the tracer shape and avoid the
  rd=x0 virtual-register rewrite (bytecode/trace operand divergence).
- Add FIELD_STORE_TO_X to field_inline_handles_rd_zero so store-to-x0 stays a
  legal no-op instead of producing an out-of-range x-register.
- Reject out-of-range field-register indices in the emulator (was silently
  clamping, hiding fr16-31 misuse until proving).
- Deduplicate the operand-shape match: expose field_inline_operand_shape_for_op
  in jolt-riscv; jolt-program and the tracer x_register_role delegate to it.
- Replace the duplicated &TraceRow JoltCycle impl with a blanket impl for &T.
- Delete the orphaned tracer inline_helpers.rs (undeclared, dead InstrAssembler).
- Drop the unused centralized LookupTables::required_prefixes table.
- Document the inv(0) and store-to-x truncation trace contracts.
- Enforce the FieldRegister bound on deserialize via a real Valid::check and
  re-run FieldInlineBytecodeMetadata::validate() on the deserialize path.
- Fix OwnedTrace::next_row so the field-inline feature actually compiles
  (TraceRow is not Copy under field-inline).
The field-inline feature is not part of host/zk/default, so no existing CI
job compiled or tested it — the feature could regress while CI stayed green.
Add an explicit clippy invocation and a nextest run over the crates that
define it (tracer, jolt-program, jolt-riscv, jolt-lookup-tables).
@0xAndoroid
0xAndoroid force-pushed the prover-stack/01-field-inline-tracing branch from 27f1951 to 8f437ff Compare June 17, 2026 04:29

@0xAndoroid 0xAndoroid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

moodlezoup added a commit that referenced this pull request Jun 18, 2026
The prover layer re-introduced the streaming prefix_mle / PrefixCheckpoint
mechanism into jolt-lookup-tables to satisfy the CPU backend's instruction
read-RAF address-phase sumcheck, which reverted the layer-01 refactor
9d4807b (#1596) that dropped it in favor of per-phase materialization.

Restore the post-refactor jolt-lookup-tables prefix API (drop prefix_mle,
update_prefix_checkpoint, and the Option-based PrefixCheckpoint alias) and
migrate the backend to compute prefixes via materialization instead:

- materialize each active prefix as a dense MLE per phase via
  Prefixes::evaluate over the phase's log_m address bits;
- bind the prefix polynomials HighToLow in lockstep with the suffix
  polynomials, reading them in read_checking_message via
  sumcheck_eval_at_0_and_2;
- checkpoint the fully-bound value at each phase boundary, replacing the
  every-two-rounds Prefixes::update_checkpoints.

The per-round sumcheck messages are unchanged, so proofs are identical.

Validated: muldiv e2e (host + host,zk), jolt-lookup-tables (307 tests),
clippy in both modes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
moodlezoup added a commit that referenced this pull request Jun 18, 2026
The prover layer re-introduced the streaming prefix_mle / PrefixCheckpoint
mechanism into jolt-lookup-tables to satisfy the CPU backend's instruction
read-RAF address-phase sumcheck, which reverted the layer-01 refactor
9d4807b (#1596) that dropped it in favor of per-phase materialization.

Restore the post-refactor jolt-lookup-tables prefix API (drop prefix_mle,
update_prefix_checkpoint, and the Option-based PrefixCheckpoint alias) and
migrate the backend to compute prefixes via materialization instead:

- materialize each active prefix as a dense MLE per phase via
  Prefixes::evaluate over the phase's log_m address bits;
- bind the prefix polynomials HighToLow in lockstep with the suffix
  polynomials, reading them in read_checking_message via
  sumcheck_eval_at_0_and_2;
- checkpoint the fully-bound value at each phase boundary, replacing the
  every-two-rounds Prefixes::update_checkpoints.

The per-round sumcheck messages are unchanged, so proofs are identical.

Validated: muldiv e2e (host + host,zk), jolt-lookup-tables (307 tests),
clippy in both modes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-spec PR has no spec file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants