Skip to content

Commit 0448e94

Browse files
authored
Rust wrapper for porgram functionality (#771)
Rust wrapper for program and symbol added Fixed extension field missing from backend implementations of program executor Signed-off-by: Koren-Brand <[email protected]>
1 parent 9ab91e7 commit 0448e94

File tree

65 files changed

+1441
-107
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1441
-107
lines changed
+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Rust FFI Bindings for Program
2+
3+
:::note
4+
Please refer to the [Program overview](../primitives/program.md) page for additional detail. This section is a brief description of the Rust FFI bindings.
5+
:::
6+
7+
This documentation is designed to bring developers up to speed about the Rust API wrapping the cpp implementation of program.
8+
9+
## Introduction
10+
Program is a class that let users define expressions on vector elements, and have ICICLE compile it for the backends for a fused implementation. This solves memory bottlenecks and also let users customize algorithms such as sumcheck. Program can create only element-wise lambda functions. Program itself works for definition while actual execution is handled through other functionalities like [Vector Operations](./vec-ops.md).
11+
12+
The following would list the implemented Rust functionality with some examples paralleling those given in the [original program overview](../primitives/program.md).
13+
# Symbol
14+
Symbol is the basic (template) class that allow users to define their own program, representing an arithmetic operation. The [function](#defining-a-function-for-program) the user define will operate on symbols.
15+
## `Symbol` Trait Definition
16+
The trait defines the functionality required by the user. The expected use-case of symbol is solely to be operated on to create the final arithmetic operation, which is reflected implemented functions and traits.
17+
```rust
18+
pub trait Symbol<F: FieldImpl>:
19+
Add<Output = Self> + Sub<Output = Self> + Mul<Output = Self> +
20+
Add<F, Output = Self> + Sub<F, Output = Self> + Mul<F, Output = Self> +
21+
AddAssign + SubAssign + MulAssign + AddAssign<F> + SubAssign<F> + MulAssign<F> +
22+
for<'a> Add<&'a Self, Output = Self> +
23+
for<'a> Sub<&'a Self, Output = Self> +
24+
for<'a> Mul<&'a Self, Output = Self> +
25+
for<'a> AddAssign<&'a Self> +
26+
for<'a> SubAssign<&'a Self> +
27+
for<'a> MulAssign<&'a Self> +
28+
Clone + Copy + Sized + Handle
29+
{
30+
fn new_input(in_idx: u32) -> Result<Self, eIcicleError>; // New input symbol for the execution function
31+
fn from_constant(constant: F) -> Result<Self, eIcicleError>; // New symbol from a field element
32+
33+
fn inverse(&self) -> Self; // Field inverse of the symbol
34+
}
35+
```
36+
## `Symbol` Struct
37+
The `Symbol` struct is implemented for each of the supported icicle fields, implementing the above trait for the specific field (field distinction is relevant for the input symbols and stored constants in the program). In its core it's just a handle to the cpp implementation.
38+
```rust
39+
pub struct Symbol {
40+
handle: SymbolHandle,
41+
}
42+
```
43+
### Traits implemented and key methods
44+
Additional traits the struct implements to fulfil `Symbol<F>` trait that should be noted.
45+
#### Arithmetic operations
46+
Symbol implements addition, subtraction and multiplication (as well as the assign variants of them) with other symbols / references as well as field elements. Applying the operations will generate a new symbol (or overwrite the existing in the case of the assign operation) representing the arithmetic operations of the two operand symbols. The `inverse` function joins these operations to allow an additional arithmetic operation (division).
47+
48+
# Program
49+
A program to be ran on the various Icicle backends. It can be either a user-defined program, or one of the members of `PredefinedProgram` enum. The program adheres to one of the following traits:
50+
## `Program` Trait Definition
51+
The trait defines the base functionality required for the user, which in this case is only creation (The execution functionality is exposed through Vector Operations). It is used as a program for running a function that takes a vector of field elements (both inputs and outputs) and has no return value (output is written to the given vector). It is executed through Vector Operations.
52+
```rust
53+
pub trait ReturningValueProgram<F>:
54+
Sized + Handle
55+
where
56+
F:FieldImpl,
57+
{
58+
type ProgSymbol: Symbol<F>;
59+
60+
fn new(program_func: impl FnOnce(&mut Vec<Self::ProgSymbol>) -> Self::ProgSymbol, nof_parameters: u32) -> Result<Self, eIcicleError>;
61+
62+
fn new_predefined(pre_def: PreDefinedProgram) -> Result<Self, eIcicleError>;
63+
}
64+
```
65+
## `Program` Struct
66+
The `Program` struct is implemented for each of the supported icicle fields, implementing the above trait for the specific field (field distinction is relevant for the input symbols and stored constants in the program). In its core it's just a handle to the cpp implementation.
67+
```rust
68+
pub struct Program {
69+
handle: ProgramHandle,
70+
}
71+
```
72+
73+
# Usage
74+
This section will outline how to use Program and Symbol, mirroring the examples from the [cpp overview](../primitives/program.md). The program use-case splits to three steps:
75+
1. Defining a function/lambda that describes the program to be ran (or choosing one of the predefined list).
76+
2. Creating a new program given the above function.
77+
3. Executing the program using the Vector Operations API.
78+
## Defining a Function for Program
79+
A function operating on a vector of symbols, with outputs being written to said input vector. The input symbols in the vector represent inputs and outputs of field elements, and will be replaced by vectors of field elements when executed.
80+
:::note
81+
The defined function defines arithmetic operations to be done in series, and could be represented as set of equations (for each output). Practically, control flow (e.g., loops, conditions) is not parsed, instead the computation follows the exact execution path taken during tracing, which determines the final computation that will be performed.
82+
:::
83+
```rust
84+
example_function<F, S>(vars: &mut Vec<S>)
85+
where
86+
F: FieldImpl,
87+
S: Symbol<F>,
88+
{
89+
let a = vars[0];
90+
let b = vars[1];
91+
let c = vars[2];
92+
let eq = vars[3];
93+
94+
vars[4] = eq * (a * b - c) + F::from_u32(9);
95+
vars[5] = a * b - c.inverse();
96+
vars[6] = vars[5];
97+
vars[3] = (vars[0] + vars[1]) * F::from_u32(2); // all variables can be both inputs and outputs
98+
}
99+
```
100+
## Creating a Program
101+
Applying the constructor with the lambda:
102+
```rust
103+
let program = Program::new(example_lambda, 7 /*nof parameters for lambda = vars.size()*/);
104+
```
105+
## Executing the Program
106+
Execution is done through the appropriate vecops function.
107+
```rust
108+
pub fn execute_program<F, Prog, Parameter>(
109+
data: &mut Vec<&Parameter>,
110+
program: &Prog,
111+
cfg: &VecOpsConfig
112+
) -> Result<(), eIcicleError>
113+
where
114+
F: FieldImpl,
115+
<F as FieldImpl>::Config: VecOps<F>,
116+
Parameter: HostOrDeviceSlice<F> + ?Sized,
117+
Prog: Program<F> + Handle,
118+
```
119+
### Examples
120+
Example taken from check_program in vec_ops tests.
121+
#### Program functionality with a custom functions
122+
```rust
123+
pub fn check_program<F, Prog>()
124+
where
125+
F: FieldImpl,
126+
<F as FieldImpl>::Config: VecOps<F> + GenerateRandom<F> + FieldArithmetic<F>,
127+
Prog: Program<F>,
128+
{
129+
let example_lambda = |vars: &mut Vec<Prog::ProgSymbol>| {
130+
let a = vars[0]; // Shallow copies pointing to the same memory in the backend
131+
let b = vars[1];
132+
let c = vars[2];
133+
let d = vars[3];
134+
135+
vars[4] = d * (a * b - c) + F::from_u32(9);
136+
vars[5] = a * b - c.inverse();
137+
vars[6] = vars[5];
138+
vars[3] = (vars[0] + vars[1]) * F::from_u32(2); // all variables can be both inputs and outputs
139+
};
140+
141+
// Additional lines for initiating the slices of field elements for the parameters
142+
143+
let mut parameters = vec![a_slice, b_slice, c_slice, eq_slice, var4_slice, var5_slice, var6_slice];
144+
145+
let program = Prog::new(example_lambda, 7).unwrap();
146+
147+
let cfg = VecOpsConfig::default();
148+
execute_program(&mut parameters, &program, &cfg).expect("Program Failed");
149+
}
150+
```
151+
#### Program functionality with predefined programs
152+
```rust
153+
pub fn check_predefined_program<F, Prog>()
154+
where
155+
F: FieldImpl,
156+
<F as FieldImpl>::Config: VecOps<F> + GenerateRandom<F> + FieldArithmetic<F>,
157+
Prog: Program<F>,
158+
{
159+
// Additional lines for initiating the slices of field elements for the parameters
160+
let mut parameters = vec![a_slice, b_slice, c_slice, eq_slice, var4_slice];
161+
162+
let program = Prog::new_predefined(PreDefinedProgram::EQtimesABminusC).unwrap();
163+
164+
let cfg = VecOpsConfig::default();
165+
execute_program(&mut parameters, &program, &cfg).expect("Program Failed");
166+
}
167+
```

docs/docs/icicle/rust-bindings/sumcheck.md

+16
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,19 @@ fn main() {
131131
let result = prover.verify(&proof, expected_sum, &transcript_config);
132132
assert!(result.is_ok() && result.unwrap(), "SumCheck proof verification failed!");
133133
}
134+
```
135+
# Misc
136+
## ReturningValueProgram
137+
A variant of [Program](./program.md) tailored for Sumcheck's combine function. It differs from `Program` by the function it receives in its constructor - instead of returning no value and using the given parameter vector as both inputs and outputs, it returns a single value which is the one and only return value of the function. This way it fulfils the utility of the combine function, allowing custom combine functions for the icicle backend.
138+
```rust
139+
pub trait ReturningValueProgram:
140+
Sized + Handle
141+
{
142+
type Field: FieldImpl;
143+
type ProgSymbol: Symbol<Self::Field>;
144+
145+
fn new(program_func: impl FnOnce(&mut Vec<Self::ProgSymbol>) -> Self::ProgSymbol, nof_parameters: u32) -> Result<Self, eIcicleError>;
146+
147+
fn new_predefined(pre_def: PreDefinedProgram) -> Result<Self, eIcicleError>;
148+
}
149+
```

docs/docs/icicle/rust-bindings/vec-ops.md

+12
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ All operations are element-wise operations, and the results placed into the `res
5757
- **`mul`**: Performs element-wise multiplication of two vectors.
5858
- **`transpose`**: Performs matrix transpose.
5959
- **`bit_reverse/bit_reverse_inplace`**: Reverse order of elements based on bit-reverse.
60+
- **`execute_program`**: Execute a user-defined function with arbitrary number of input and output variables (Passed as part of data - a vector of slices of field elements). For more details see [program](./program.md).
6061

6162

6263

@@ -106,4 +107,15 @@ pub fn bit_reverse_inplace<F>(
106107
input: &mut (impl HostOrDeviceSlice<F> + ?Sized),
107108
cfg: &VecOpsConfig,
108109
) -> Result<(), eIcicleError>;
110+
111+
pub fn execute_program<F, Prog, Data>(
112+
data: &mut Vec<&Data>,
113+
program: &Prog,
114+
cfg: &VecOpsConfig
115+
) -> Result<(), eIcicleError>
116+
where
117+
F: FieldImpl,
118+
<F as FieldImpl>::Config: VecOps<F>,
119+
Data: HostOrDeviceSlice<F> + ?Sized,
120+
Prog: Program<F>;
109121
```

examples/rust/sumcheck/src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ pub fn main() {
125125
);
126126
//try different combine functions!
127127
let combine_function =
128-
<icicle_bn254::program::FieldReturningValueProgram as ReturningValueProgram>::new_predefined(
128+
<icicle_bn254::program::bn254::FieldReturningValueProgram as ReturningValueProgram>::new_predefined(
129129
PreDefinedProgram::EQtimesABminusC,
130130
)
131131
.unwrap();

icicle/backend/cpu/src/field/cpu_vec_ops.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1005,4 +1005,5 @@ REGISTER_VECTOR_PRODUCT_EXT_FIELD_BACKEND("CPU", cpu_vector_product<extension_t>
10051005
REGISTER_SCALAR_MUL_VEC_EXT_FIELD_BACKEND("CPU", cpu_scalar_mul<extension_t>);
10061006
REGISTER_SCALAR_ADD_VEC_EXT_FIELD_BACKEND("CPU", cpu_scalar_add<extension_t>);
10071007
REGISTER_SCALAR_SUB_VEC_EXT_FIELD_BACKEND("CPU", cpu_scalar_sub<extension_t>);
1008+
REGISTER_EXECUTE_PROGRAM_EXT_FIELD_BACKEND("CPU", cpu_execute_program<extension_t>);
10081009
#endif // EXT_FIELD

icicle/cmake/target_editor.cmake

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ function(handle_field TARGET)
77
src/fields/ffi_extern.cpp
88
src/vec_ops.cpp
99
src/matrix_ops.cpp
10+
src/program/program_c_api.cpp
11+
src/symbol/symbol_api.cpp
1012
)
1113
endfunction()
1214

icicle/include/icicle/backend/vec_ops_backend.h

+17
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,23 @@ namespace icicle {
460460
return true; \
461461
}(); \
462462
}
463+
464+
using extProgramExecutionImpl = std::function<eIcicleError(
465+
const Device& device,
466+
std::vector<extension_t*>& data,
467+
const Program<extension_t>& program,
468+
uint64_t size,
469+
const VecOpsConfig& config)>;
470+
471+
void register_extension_execute_program(const std::string& deviceType, extProgramExecutionImpl);
472+
473+
#define REGISTER_EXECUTE_PROGRAM_EXT_FIELD_BACKEND(DEVICE_TYPE, FUNC) \
474+
namespace { \
475+
static bool UNIQUE(_reg_program_execution) = []() -> bool { \
476+
register_extension_execute_program(DEVICE_TYPE, FUNC); \
477+
return true; \
478+
}(); \
479+
}
463480
#endif // EXT_FIELD
464481

465482
} // namespace icicle

icicle/include/icicle/program/program.h

+10
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ namespace icicle {
116116
// default constructor
117117
Program() {}
118118

119+
// Friend function for C-api to have access to the default constructor
120+
template <typename T>
121+
friend Program<T>* create_empty_program();
122+
119123
// run recursively on the DFG and push instruction per operation
120124
void generate_program(std::shared_ptr<Operation<S>> operation)
121125
{
@@ -206,4 +210,10 @@ namespace icicle {
206210
}
207211
};
208212

213+
// Friend function to access the protected default program constructors
214+
template <typename S>
215+
Program<S>* create_empty_program()
216+
{
217+
return new Program<S>();
218+
}
209219
} // namespace icicle

icicle/include/icicle/program/returning_value_program.h

+14
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,21 @@ namespace icicle {
4444

4545
int get_polynomial_degree() const { return m_poly_degree; }
4646

47+
protected:
48+
ReturningValueProgram() {}
49+
50+
// Friend function for C-api to have access to the default constructor
51+
template <typename T>
52+
friend ReturningValueProgram<T>* create_empty_returning_value_program();
53+
4754
private:
4855
int m_poly_degree = 0;
4956
};
57+
58+
// Friend function for C-api to have access to the default constructor
59+
template <typename S>
60+
ReturningValueProgram<S>* create_empty_returning_value_program()
61+
{
62+
return new ReturningValueProgram<S>();
63+
}
5064
} // namespace icicle

0 commit comments

Comments
 (0)