|
| 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 | +``` |
0 commit comments