Skip to content

fix(values): encode BoundedInt in its compact representation; deref boxed returns - #1657

Merged
TomerStarkware merged 1 commit into
mainfrom
tomer/values_bounded_int_box_return
Aug 23, 2026
Merged

fix(values): encode BoundedInt in its compact representation; deref boxed returns#1657
TomerStarkware merged 1 commit into
mainfrom
tomer/values_bounded_int_box_return

Conversation

@TomerStarkware

@TomerStarkware TomerStarkware commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #1655 and the nested-Box/Nullable PR.

Problem

Two remaining marshaling asymmetries:

  1. BoundedInt encode: to_ptr wrote the un-biased value into a 32-byte felt slot, while the native representation (and what from_ptr decodes) is the compact one — value - lower stored in get_integer_layout(repr_bit_width()). Nested in an aggregate (top-level args are still gated by the arch.rs panic, implement AbiArgument::to_ptr for Value::BoundedInt #1217), it was both silently mis-decoded and overran its slot: Array<BoundedInt<3, 10>> has a 1-byte stride and got 32 bytes written per element. The existing unit test test_to_ptr_bounded_int_valid asserted the buggy encoding.

  2. Boxed returns: parse_result passed a returned Box<T>'s return-pointer slot straight to from_ptr without dereferencing it. Any function using a non-ZST builtin returns through a return pointer, so e.g. fn(...) -> Box<felt252> using pedersen decoded two raw heap addresses as the payload.

Fix

  • Add RangeExt::repr_encode / repr_decode as the single implementation of the compact encoding (felt-wrapping subtraction so negative lower bounds round-trip). to_ptr uses the encode side — allocating the actual repr_bit_width layout and validating against the type's range — and both existing decode duplicates (Value::from_ptr and the register path in parse_result) now share the decode side.
  • parse_result's Box arm dereferences the return-pointer slot once, mirroring the Nullable arm.
  • The arch.rs BoundedInt panic (implement AbiArgument::to_ptr for Value::BoundedInt #1217) stays, with a TODO noting it can now be implemented on top of repr_encode.

Tests

  • VM-vs-native: boxed return forced through the return pointer (pedersen builtin).
  • Round-trip unit tests: Array<BoundedInt<3, 10>> asserting the 1-byte biased element buffer ([0, 4, 6] for values [3, 7, 9]), and a negative-lower range (BoundedInt<-5, 5>).
  • test_to_ptr_bounded_int_valid corrected: value 16 in BoundedInt<10, 510> encodes as [6, 0] in 2 bytes (and its embedded range fixed to the parsed [10, 511)).

🤖 Generated with Claude Code


This change is Reviewable

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

✅ Code is now correctly formatted.

@TomerStarkware
TomerStarkware force-pushed the tomer/values_nested_box_nullable branch from 9bed290 to c09a4f1 Compare August 18, 2026 09:43
@TomerStarkware
TomerStarkware force-pushed the tomer/values_bounded_int_box_return branch from bff9388 to 44e5111 Compare August 18, 2026 09:43
@TomerStarkware
TomerStarkware force-pushed the tomer/values_nested_box_nullable branch from c09a4f1 to b7dd582 Compare August 18, 2026 15:39
@TomerStarkware
TomerStarkware force-pushed the tomer/values_bounded_int_box_return branch 2 times, most recently from 86ba869 to cc5f00f Compare August 19, 2026 16:07
@TomerStarkware
TomerStarkware force-pushed the tomer/values_nested_box_nullable branch from b7dd582 to 3d5c68c Compare August 19, 2026 16:07
@TomerStarkware
TomerStarkware requested a review from orizi August 20, 2026 12:28

@orizi orizi 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:

@orizi reviewed 6 files and all commit messages, and made 1 comment.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on TomerStarkware).

@TomerStarkware
TomerStarkware force-pushed the tomer/values_nested_box_nullable branch from 3d5c68c to e42ae5e Compare August 23, 2026 10:33
Base automatically changed from tomer/values_nested_box_nullable to main August 23, 2026 11:54
…oxed returns

Two remaining marshaling asymmetries:

- `Value::to_ptr` wrote a BoundedInt as the un-biased value in a 32-byte
  felt slot, while the native representation (and `from_ptr`) is the
  compact one: `value - lower` stored in `repr_bit_width()` bits. A
  BoundedInt nested in an aggregate (top-level arguments are still gated
  by the arch.rs panic, #1217) was both silently mis-decoded and
  overran its slot, corrupting neighboring elements —
  `Array<BoundedInt<3, 10>>` returned garbage.

- `parse_result` passed a returned `Box<T>`'s return-pointer slot
  straight to `from_ptr` without dereferencing it. Any function that
  uses a non-ZST builtin returns through a return pointer, so e.g.
  `fn(...) -> Box<felt252>` using pedersen decoded two raw heap
  addresses as the payload. Mirror the Nullable arm's deref.

Add `RangeExt::repr_encode`/`repr_decode` as the single implementation
of the compact encoding (felt-wrapping subtraction so negative lower
bounds round-trip) and use the decode side from both existing
duplicates (`Value::from_ptr` and the register path in `parse_result`).
The `to_ptr` arm now also validates against the type's range rather
than only the value's embedded one.

Fixes `test_to_ptr_bounded_int_valid`, which asserted the buggy 32-byte
encoding ([16, ...] instead of `value - lower` = [6, 0] in the 2-byte
9-bit representation), and its embedded range, which didn't match the
type's (`BoundedInt<10, 510>` parses to the range `[10, 511)`).

Adds a VM-vs-native test for a boxed return forced through the return
pointer, and round-trip unit tests for `Array<BoundedInt<3, 10>>`
(asserting the 1-byte biased element buffer) and a negative-lower
range.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@TomerStarkware
TomerStarkware force-pushed the tomer/values_bounded_int_box_return branch from cc5f00f to e9f2176 Compare August 23, 2026 12:21
@github-actions

Copy link
Copy Markdown

Benchmarking results

Benchmark for program dict_insert

Open benchmarks
Command Mean [s] Min [s] Max [s] Relative
Cairo-vm (Rust, Cairo 1) 5.767 ± 0.053 5.695 5.888 5.56 ± 0.06
cairo-native (embedded AOT) 1.037 ± 0.006 1.030 1.047 1.00
cairo-native (embedded JIT using LLVM's ORC Engine) 1.044 ± 0.005 1.039 1.052 1.01 ± 0.01

Benchmark for program dict_snapshot

Open benchmarks
Command Mean [ms] Min [ms] Max [ms] Relative
Cairo-vm (Rust, Cairo 1) 306.2 ± 1.8 302.5 308.4 1.00
cairo-native (embedded AOT) 929.8 ± 8.4 918.9 942.5 3.04 ± 0.03
cairo-native (embedded JIT using LLVM's ORC Engine) 946.7 ± 3.3 942.8 951.8 3.09 ± 0.02

Benchmark for program factorial_2M

Open benchmarks
Command Mean [s] Min [s] Max [s] Relative
Cairo-vm (Rust, Cairo 1) 2.499 ± 0.038 2.465 2.584 2.60 ± 0.04
cairo-native (embedded AOT) 0.963 ± 0.004 0.957 0.970 1.00
cairo-native (embedded JIT using LLVM's ORC Engine) 0.973 ± 0.003 0.968 0.976 1.01 ± 0.01

Benchmark for program fib_2M

Open benchmarks
Command Mean [s] Min [s] Max [s] Relative
Cairo-vm (Rust, Cairo 1) 2.419 ± 0.029 2.386 2.484 2.59 ± 0.04
cairo-native (embedded AOT) 0.933 ± 0.009 0.924 0.957 1.00
cairo-native (embedded JIT using LLVM's ORC Engine) 0.945 ± 0.003 0.940 0.950 1.01 ± 0.01

Benchmark for program linear_search

Open benchmarks
Command Mean [ms] Min [ms] Max [ms] Relative
Cairo-vm (Rust, Cairo 1) 332.4 ± 3.9 326.4 339.0 1.00
cairo-native (embedded AOT) 945.5 ± 2.9 942.4 952.5 2.84 ± 0.03
cairo-native (embedded JIT using LLVM's ORC Engine) 960.7 ± 4.4 954.8 966.7 2.89 ± 0.04

Benchmark for program logistic_map

Open benchmarks
Command Mean [ms] Min [ms] Max [ms] Relative
Cairo-vm (Rust, Cairo 1) 282.3 ± 1.5 279.8 284.2 1.00
cairo-native (embedded AOT) 932.9 ± 4.9 924.4 939.8 3.30 ± 0.02
cairo-native (embedded JIT using LLVM's ORC Engine) 951.4 ± 3.4 948.0 957.2 3.37 ± 0.02

@github-actions

Copy link
Copy Markdown

Benchmark results Main vs HEAD.

Base

Command Mean [s] Min [s] Max [s] Relative
base dict_insert.cairo (JIT) 1.827 ± 0.017 1.808 1.853 1.01 ± 0.02
base dict_insert.cairo (AOT) 1.812 ± 0.027 1.769 1.847 1.00

Head

Command Mean [s] Min [s] Max [s] Relative
head dict_insert.cairo (JIT) 1.879 ± 0.023 1.846 1.914 1.01 ± 0.01
head dict_insert.cairo (AOT) 1.852 ± 0.015 1.833 1.885 1.00

Base

Command Mean [s] Min [s] Max [s] Relative
base dict_snapshot.cairo (JIT) 1.637 ± 0.018 1.615 1.670 1.03 ± 0.01
base dict_snapshot.cairo (AOT) 1.588 ± 0.008 1.575 1.602 1.00

Head

Command Mean [s] Min [s] Max [s] Relative
head dict_snapshot.cairo (JIT) 1.709 ± 0.019 1.683 1.739 1.01 ± 0.01
head dict_snapshot.cairo (AOT) 1.686 ± 0.016 1.657 1.718 1.00

Base

Command Mean [s] Min [s] Max [s] Relative
base factorial_2M.cairo (JIT) 1.680 ± 0.013 1.658 1.706 1.01 ± 0.01
base factorial_2M.cairo (AOT) 1.658 ± 0.009 1.643 1.674 1.00

Head

Command Mean [s] Min [s] Max [s] Relative
head factorial_2M.cairo (JIT) 1.768 ± 0.007 1.758 1.778 1.01 ± 0.01
head factorial_2M.cairo (AOT) 1.752 ± 0.018 1.721 1.775 1.00

Base

Command Mean [s] Min [s] Max [s] Relative
base fib_2M.cairo (JIT) 1.626 ± 0.008 1.610 1.637 1.01 ± 0.01
base fib_2M.cairo (AOT) 1.614 ± 0.017 1.593 1.641 1.00

Head

Command Mean [s] Min [s] Max [s] Relative
head fib_2M.cairo (JIT) 1.757 ± 0.014 1.733 1.778 1.01 ± 0.01
head fib_2M.cairo (AOT) 1.733 ± 0.010 1.711 1.743 1.00

Base

Command Mean [s] Min [s] Max [s] Relative
base linear_search.cairo (JIT) 1.655 ± 0.009 1.641 1.673 1.00 ± 0.01
base linear_search.cairo (AOT) 1.654 ± 0.020 1.627 1.683 1.00

Head

Command Mean [s] Min [s] Max [s] Relative
head linear_search.cairo (JIT) 1.776 ± 0.017 1.752 1.811 1.03 ± 0.01
head linear_search.cairo (AOT) 1.731 ± 0.012 1.708 1.742 1.00

Base

Command Mean [s] Min [s] Max [s] Relative
base logistic_map.cairo (JIT) 1.668 ± 0.028 1.633 1.712 1.01 ± 0.02
base logistic_map.cairo (AOT) 1.645 ± 0.020 1.621 1.686 1.00

Head

Command Mean [s] Min [s] Max [s] Relative
head logistic_map.cairo (JIT) 1.752 ± 0.017 1.726 1.792 1.04 ± 0.01
head logistic_map.cairo (AOT) 1.685 ± 0.011 1.669 1.701 1.00

@TomerStarkware
TomerStarkware added this pull request to the merge queue Aug 23, 2026
Merged via the queue into main with commit 58ddd21 Aug 23, 2026
15 checks passed
@TomerStarkware
TomerStarkware deleted the tomer/values_bounded_int_box_return branch August 23, 2026 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants