Skip to content

Commit c9c0989

Browse files
feat: Lazily call setup function for moduli and curves on first use (#1603)
We no longer need to manually call `setup_*` at the start of main for the moduli and curves. Instead, setup is automatically done on first use of each modulus or curve. Depends on #1596 --------- Co-authored-by: Jonathan Wang <[email protected]>
1 parent 91e9974 commit c9c0989

File tree

39 files changed

+139
-198
lines changed

39 files changed

+139
-198
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

benchmarks/guest/ecrecover/src/main.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ openvm_ecc_guest::sw_macros::sw_init! {
2424
}
2525

2626
pub fn main() {
27-
setup_all_moduli();
28-
setup_all_curves();
29-
3027
let expected_address = read_vec();
3128
for _ in 0..5 {
3229
let input = read_vec();

benchmarks/guest/kitchen-sink/src/main.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,8 @@ openvm_algebra_guest::complex_macros::complex_init! {
4848
}
4949

5050
pub fn main() {
51-
// Setup will materialize every chip
52-
setup_all_moduli();
53-
setup_all_complex_extensions();
54-
setup_all_curves();
51+
// TODO: Since we don't explicitly call setup functions anymore, we should rewrite this test
52+
// to use every declared modulus and curve to ensure that every chip is materialized.
5553

5654
let [one, six] = [1, 6].map(Seven::from_u32);
5755
assert_eq!(one + six, Seven::ZERO);

benchmarks/guest/pairing/src/main.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ openvm_algebra_guest::complex_macros::complex_init! {
2222
const PAIR_ELEMENT_LEN: usize = 32 * (2 + 4); // 1 G1Affine (2 Fp), 1 G2Affine (4 Fp)
2323

2424
pub fn main() {
25-
setup_all_moduli();
26-
setup_all_complex_extensions();
27-
// Pairing doesn't need G1Affine intrinsics, but we trigger it anyways to test the chips
28-
setup_all_curves();
29-
3025
// copied from https://github.com/bluealloy/revm/blob/9e39df5dbc5fdc98779c644629b28b8bee75794a/crates/precompile/src/bn128.rs#L395
3126
let input = hex::decode(
3227
"\

book/src/custom-extensions/algebra.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,10 @@ moduli_init! {
4646

4747
This step enumerates the declared moduli (e.g., `0` for the first one, `1` for the second one) and sets up internal linkage so the compiler can generate the appropriate RISC-V instructions associated with each modulus.
4848

49-
3. **Setup**: At runtime, before performing arithmetic, a setup instruction must be sent to ensure security and correctness. For the \\(i\\)-th modulus, you call `setup_<i>()` (e.g., `setup_0()` or `setup_1()`). Alternatively, `setup_all_moduli()` can be used to handle all declared moduli.
50-
5149
**Summary**:
5250

5351
- `moduli_declare!`: Declares modular arithmetic structures and can be done multiple times.
5452
- `init!`: Called once in the final binary to assign and lock in the moduli.
55-
- `setup_<i>()`/`setup_all_moduli()`: Ensures at runtime that the correct modulus is in use, providing a security check and finalizing the environment for safe arithmetic operations.
5653

5754
## Complex field extension
5855

@@ -83,8 +80,6 @@ complex_init! {
8380
*/
8481
```
8582

86-
3. **Setup**: Similar to moduli, call `setup_complex_<i>()` or `setup_all_complex_extensions()` at runtime to secure the environment.
87-
8883
### Config parameters
8984

9085
For the guest program to build successfully, all used moduli must be declared in the `.toml` config file in the following format:

book/src/custom-extensions/ecc.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,10 @@ sw_init! {
5454
*/
5555
```
5656

57-
3. **Setup**: Similar to the moduli and complex extensions, runtime setup instructions ensure that the correct curve parameters are being used, guaranteeing secure operation.
58-
5957
**Summary**:
6058

6159
- `sw_declare!`: Declares elliptic curve structures.
6260
- `init!`: Initializes them once, linking them to the underlying moduli.
63-
- `setup_sw_<i>()`/`setup_all_curves()`: Secures runtime correctness.
6461

6562
To use elliptic curve operations on a struct defined with `sw_declare!`, it is expected that the struct for the curve's coordinate field was defined using `moduli_declare!`. In particular, the coordinate field needs to be initialized and set up as described in the [algebra extension](./algebra.md) chapter.
6663

book/src/custom-extensions/pairing.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,6 @@ Additionally, we'll need to initialize our moduli and `Fp2` struct via the follo
3636
{{ #include ../../../examples/pairing/src/main.rs:init }}
3737
```
3838

39-
And we'll run the required setup functions at the top of the guest program's `main()` function:
40-
41-
```rust,no_run,noplayground
42-
{{ #include ../../../examples/pairing/src/main.rs:setup }}
43-
```
44-
45-
There are two moduli defined internally in the `Bls12_381` feature. The `moduli_init!` macro thus requires both of them to be initialized. However, we do not need the scalar field of BLS12-381 (which is at index 1), and thus we only initialize the modulus from index 0, thus we only use `setup_0()` (as opposed to `setup_all_moduli()`, which will save us some columns when generating the trace).
46-
4739
## Input values
4840

4941
The inputs to the pairing check are `AffinePoint`s in \\(\mathbb{F}\_p\\) and \\(\mathbb{F}\_{p^2}\\). They can be constructed via the `AffinePoint::new` function, with the inner `Fp` and `Fp2` values constructed via various `from_...` functions.

docs/specs/RISCV.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ generates classes `Bls12381` and `Bn254` that represent the elements of the corr
132132

133133
### Field Arithmetic
134134

135-
For each created modular class, one must call a corresponding `setup_*` function once at the beginning of the program. For example, for the structs above this would be `setup_0()` and `setup_1()`. This function generates the `setup` intrinsics which are distinguished by the `rs2` operand that specifies the chip this instruction is passed to..
135+
For each created modular class, one must call a corresponding `setup_*` function before using the intrinsics.
136+
For example, for the structs above this would be `setup_0()` and `setup_1()`. This function generates the `setup` intrinsics which are distinguished by the `rs2` operand that specifies the chip this instruction is passed to.
137+
For developer convenience, in the Rust function bindings for these intrinsics, each modulus's `setup_*` function is automatically called on the first use of any of its intrinsics.
136138

137139
We use `config.mod_idx(N)` to denote the index of `N` in this list. In the list below, `idx` denotes `config.mod_idx(N)`.
138140

examples/algebra/src/main.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ openvm_algebra_complex_macros::complex_init! {
3131
*/
3232

3333
pub fn main() {
34-
// Since we only use an arithmetic operation with `Mod1` and not `Mod2`,
35-
// we only need to call `setup_0()` here.
36-
setup_0();
37-
setup_all_complex_extensions();
3834
let a = Complex1::new(Mod1::ZERO, Mod1::from_u32(0x3b8) * Mod1::from_u32(0x100000)); // a = -i in the corresponding field
3935
let b = Complex2::new(Mod2::ZERO, Mod2::from_u32(1000000006)); // b = -i in the corresponding field
4036
assert_eq!(a.clone() * &a * &a * &a * &a, a); // a^5 = a

examples/ecc/src/main.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ openvm_ecc_guest::sw_macros::sw_init! {
2323

2424
// ANCHOR: main
2525
pub fn main() {
26-
setup_all_moduli();
27-
setup_all_curves();
2826
let x1 = Secp256k1Coord::from_u32(1);
2927
let y1 = Secp256k1Coord::from_le_bytes(&hex!(
3028
"EEA7767E580D75BC6FDD7F58D2A84C2614FB22586068DB63B346C6E60AF21842"

examples/pairing/src/main.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,6 @@ openvm_algebra_complex_macros::complex_init! {
2626

2727
// ANCHOR: main
2828
pub fn main() {
29-
// ANCHOR: setup
30-
setup_0();
31-
setup_all_complex_extensions();
32-
// ANCHOR_END: setup
33-
3429
let p0 = AffinePoint::new(
3530
Fp::from_be_bytes(&hex!("17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb")),
3631
Fp::from_be_bytes(&hex!("08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1"))

extensions/algebra/complex-macros/README.md

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ openvm_algebra_complex_macros::complex_init! {
2727
*/
2828

2929
pub fn main() {
30-
setup_all_moduli();
31-
setup_all_complex_extensions();
3230
// ...
3331
}
3432
```
@@ -68,24 +66,23 @@ extern "C" {
6866
2. Again, `complex_init!` macro implements these extern functions and defines the setup functions for the complex arithmetic struct.
6967

7068
```rust
69+
#[allow(non_snake_case)]
7170
#[cfg(target_os = "zkvm")]
7271
mod openvm_intrinsics_ffi_complex {
73-
fn complex_add_extern_func_Complex(rd: usize, rs1: usize, rs2: usize) {
72+
#[no_mangle]
73+
extern "C" fn complex_add_extern_func_Complex(rd: usize, rs1: usize, rs2: usize) {
7474
// send the instructions for the corresponding complex chip
7575
// If this struct was `init`ed k-th, these operations will be sent to the k-th complex chip
7676
}
77-
// implement the other functions
78-
}
79-
pub fn setup_complex_0() {
80-
// send the setup instructions
81-
}
82-
pub fn setup_all_complex_extensions() {
83-
setup_complex_0();
84-
// call all other setup_complex_* for all the items in the moduli_init! macro
77+
// .. implement the other functions
78+
#[no_mangle]
79+
extern "C" fn complex_setup_extern_func_Complex() {
80+
// send the setup instructions
81+
}
8582
}
8683
```
8784

88-
3. Obviously, `mod_idx` in the `complex_init!` must match the position of the corresponding modulus in the `moduli_init!` macro. The order of the items in `complex_init!` affects what `setup_complex_*` function will correspond to what complex class. Also, it **must match** the order of the moduli in the chip configuration -- more specifically, in the modular extension parameters (the order of numbers in `Fp2Extension::supported_modulus`, which is usually defined with the whole `app_vm_config` in the `openvm.toml` file). However, it again imposes the restriction that we only can invoke `complex_init!` once. Again analogous to the moduli setups, we must call `setup_complex_*` for each used complex extension before doing anything with entities of that class (or one can call `setup_all_complex_extensions` to setup all of them, if all are used).
85+
3. Obviously, `mod_idx` in the `complex_init!` must match the position of the corresponding modulus in the `moduli_init!` macro. The order of the items in `complex_init!` affects what `setup_complex_*` function will correspond to what complex class. Also, it **must match** the order of the moduli in the chip configuration -- more specifically, in the modular extension parameters (the order of numbers in `Fp2Extension::supported_modulus`, which is usually defined with the whole `app_vm_config` in the `openvm.toml` file). However, it again imposes the restriction that we only can invoke `complex_init!` once. Again analogous to the moduli setups, the rust bindings will automatically call `complex_setup_extern_func_*` on each complex extension on first use of its intrinsics.
8986

9087
4. Note that, due to the nature of function names, the name of the struct used in `complex_init!` must be the same as in `complex_declare!`. To illustrate, the following code will **fail** to compile:
9188

@@ -107,4 +104,4 @@ The reason is that, for example, the function `complex_add_extern_func_Bn254Fp2`
107104

108105
5. `cargo openvm build` will automatically generate a call to `complex_init!` based on `openvm.toml`.
109106
Note that `openvm.toml` must list the supported moduli as pairs `(name, modulus)` where `name` is the name of the struct created by `complex_declare!` as a string (in the example at the top of this document, its `"Complex"`).
110-
The SDK also supports this feature.
107+
The SDK also supports this feature.

extensions/algebra/complex-macros/src/lib.rs

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ pub fn complex_declare(input: TokenStream) -> TokenStream {
5858
create_extern_func!(complex_sub_extern_func);
5959
create_extern_func!(complex_mul_extern_func);
6060
create_extern_func!(complex_div_extern_func);
61+
create_extern_func!(complex_setup_extern_func);
6162

6263
let result = TokenStream::from(quote::quote_spanned! { span.into() =>
6364
extern "C" {
6465
fn #complex_add_extern_func(rd: usize, rs1: usize, rs2: usize);
6566
fn #complex_sub_extern_func(rd: usize, rs1: usize, rs2: usize);
6667
fn #complex_mul_extern_func(rd: usize, rs1: usize, rs2: usize);
6768
fn #complex_div_extern_func(rd: usize, rs1: usize, rs2: usize);
69+
fn #complex_setup_extern_func();
6870
}
6971

7072

@@ -110,6 +112,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream {
110112
}
111113
#[cfg(target_os = "zkvm")]
112114
{
115+
Self::assert_is_setup();
113116
unsafe {
114117
#complex_add_extern_func(
115118
self as *mut Self as usize,
@@ -130,6 +133,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream {
130133
}
131134
#[cfg(target_os = "zkvm")]
132135
{
136+
Self::assert_is_setup();
133137
unsafe {
134138
#complex_sub_extern_func(
135139
self as *mut Self as usize,
@@ -154,6 +158,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream {
154158
}
155159
#[cfg(target_os = "zkvm")]
156160
{
161+
Self::assert_is_setup();
157162
unsafe {
158163
#complex_mul_extern_func(
159164
self as *mut Self as usize,
@@ -179,6 +184,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream {
179184
}
180185
#[cfg(target_os = "zkvm")]
181186
{
187+
Self::assert_is_setup();
182188
unsafe {
183189
#complex_div_extern_func(
184190
self as *mut Self as usize,
@@ -199,6 +205,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream {
199205
}
200206
#[cfg(target_os = "zkvm")]
201207
{
208+
Self::assert_is_setup();
202209
let mut uninit: core::mem::MaybeUninit<Self> = core::mem::MaybeUninit::uninit();
203210
unsafe {
204211
#complex_add_extern_func(
@@ -222,6 +229,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream {
222229
}
223230
#[cfg(target_os = "zkvm")]
224231
{
232+
Self::assert_is_setup();
225233
let mut uninit: core::mem::MaybeUninit<Self> = core::mem::MaybeUninit::uninit();
226234
unsafe {
227235
#complex_sub_extern_func(
@@ -249,6 +257,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream {
249257
}
250258
#[cfg(target_os = "zkvm")]
251259
{
260+
Self::assert_is_setup();
252261
unsafe {
253262
#complex_mul_extern_func(
254263
dst_ptr as usize,
@@ -270,6 +279,7 @@ pub fn complex_declare(input: TokenStream) -> TokenStream {
270279
}
271280
#[cfg(target_os = "zkvm")]
272281
{
282+
Self::assert_is_setup();
273283
let mut uninit: core::mem::MaybeUninit<Self> = core::mem::MaybeUninit::uninit();
274284
unsafe {
275285
#complex_div_extern_func(
@@ -281,6 +291,15 @@ pub fn complex_declare(input: TokenStream) -> TokenStream {
281291
unsafe { uninit.assume_init() }
282292
}
283293
}
294+
295+
// Helper function to call the setup instruction on first use
296+
fn assert_is_setup() {
297+
static is_setup: ::openvm_algebra_guest::once_cell::race::OnceBool = ::openvm_algebra_guest::once_cell::race::OnceBool::new();
298+
is_setup.get_or_init(|| {
299+
unsafe { #complex_setup_extern_func(); }
300+
true
301+
});
302+
}
284303
}
285304

286305
impl openvm_algebra_guest::field::ComplexConjugate for #struct_name {
@@ -527,8 +546,6 @@ pub fn complex_init(input: TokenStream) -> TokenStream {
527546
let MacroArgs { items } = parse_macro_input!(input as MacroArgs);
528547

529548
let mut externs = Vec::new();
530-
let mut setups = Vec::new();
531-
let mut setup_all_complex_extensions = Vec::new();
532549

533550
let span = proc_macro::Span::call_site();
534551

@@ -587,22 +604,22 @@ pub fn complex_init(input: TokenStream) -> TokenStream {
587604
});
588605
}
589606

590-
let setup_function =
591-
syn::Ident::new(&format!("setup_complex_{}", complex_idx), span.into());
607+
let setup_extern_func = syn::Ident::new(
608+
&format!("complex_setup_extern_func_{}", struct_name),
609+
span.into(),
610+
);
592611

593-
setup_all_complex_extensions.push(quote::quote_spanned! { span.into() =>
594-
#setup_function();
595-
});
596-
setups.push(quote::quote_spanned! { span.into() =>
597-
#[allow(non_snake_case)]
598-
pub fn #setup_function() {
612+
externs.push(quote::quote_spanned! { span.into() =>
613+
#[no_mangle]
614+
extern "C" fn #setup_extern_func() {
599615
#[cfg(target_os = "zkvm")]
600616
{
601-
let two_modulus_bytes = &openvm_intrinsics_meta_do_not_type_this_by_yourself::two_modular_limbs_list[openvm_intrinsics_meta_do_not_type_this_by_yourself::limb_list_borders[#mod_idx]..openvm_intrinsics_meta_do_not_type_this_by_yourself::limb_list_borders[#mod_idx + 1]];
617+
use super::openvm_intrinsics_meta_do_not_type_this_by_yourself::{two_modular_limbs_list, limb_list_borders};
618+
let two_modulus_bytes = &two_modular_limbs_list[limb_list_borders[#mod_idx]..limb_list_borders[#mod_idx + 1]];
602619

603620
// We are going to use the numeric representation of the `rs2` register to distinguish the chip to setup.
604621
// The transpiler will transform this instruction, based on whether `rs2` is `x0` or `x1`, into a `SETUP_ADDSUB` or `SETUP_MULDIV` instruction.
605-
let mut uninit: core::mem::MaybeUninit<[u8; openvm_intrinsics_meta_do_not_type_this_by_yourself::limb_list_borders[#mod_idx + 1] - openvm_intrinsics_meta_do_not_type_this_by_yourself::limb_list_borders[#mod_idx]]> = core::mem::MaybeUninit::uninit();
622+
let mut uninit: core::mem::MaybeUninit<[u8; limb_list_borders[#mod_idx + 1] - limb_list_borders[#mod_idx]]> = core::mem::MaybeUninit::uninit();
606623
openvm::platform::custom_insn_r!(
607624
opcode = ::openvm_algebra_guest::OPCODE,
608625
funct3 = ::openvm_algebra_guest::COMPLEX_EXT_FIELD_FUNCT3,
@@ -629,14 +646,11 @@ pub fn complex_init(input: TokenStream) -> TokenStream {
629646
}
630647

631648
TokenStream::from(quote::quote_spanned! { span.into() =>
649+
#[allow(non_snake_case)]
632650
#[cfg(target_os = "zkvm")]
633651
mod openvm_intrinsics_ffi_complex {
634652
#(#externs)*
635653
}
636-
#(#setups)*
637-
pub fn setup_all_complex_extensions() {
638-
#(#setup_all_complex_extensions)*
639-
}
640654
})
641655
}
642656

extensions/algebra/guest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ openvm-algebra-moduli-macros = { workspace = true }
1212
openvm-algebra-complex-macros = { workspace = true }
1313
serde-big-array.workspace = true
1414
strum_macros.workspace = true
15+
once_cell = { workspace = true, features = ["race", "alloc"] }
1516

1617
[target.'cfg(not(target_os = "zkvm"))'.dependencies]
1718
num-bigint.workspace = true

extensions/algebra/guest/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ mod halo2curves;
6868
/// Exponentiation by bytes
6969
mod exp_bytes;
7070
pub use exp_bytes::*;
71+
pub use once_cell;
7172

7273
/// Division operation that is undefined behavior when the denominator is not invertible.
7374
pub trait DivUnsafe<Rhs = Self>: Sized {

0 commit comments

Comments
 (0)