Skip to content

Commit c7a9a47

Browse files
Optimization: lazy conversion of circuit outputs to struct form (#1348)
* No integer to struct in circuit outputs * Improve documentation on types/circuit.rs * Nitpick * Document gate_evaluation better * Rename variable * Rename variable * Rename value to gates * Add ok block * Improve documentation * Rename variable * Fix up the eval building * Improve type documentation * Minor fix * Nitpicks * Fix some docs * Remove TODO as its not valid - Also improve some docs
1 parent 21cc66c commit c7a9a47

File tree

2 files changed

+69
-65
lines changed

2 files changed

+69
-65
lines changed

src/libfuncs/circuit.rs

Lines changed: 61 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ fn build_eval<'ctx, 'this>(
313313
let circuit_data = entry.arg(3)?;
314314
let circuit_modulus = entry.arg(4)?;
315315

316-
// arguments 5 and 6 are used to build the gate 0 (with constant value 1)
316+
// Arguments 5 and 6 are used to build the gate 0 (with constant value 1).
317317
// let zero = entry.argument(5)?;
318318
// let one = entry.argument(6)?;
319319

@@ -361,19 +361,11 @@ fn build_eval<'ctx, 'this>(
361361
circuit_info.mul_offsets.len() * MUL_MOD_BUILTIN_SIZE,
362362
)?;
363363

364-
// convert circuit output from integer representation to struct representation
365-
let gates = gates
366-
.into_iter()
367-
.map(|value| u384_integer_to_struct(context, ok_block, location, value))
368-
.collect::<Result<Vec<_>>>()?;
369-
370-
// Calculate full capacity for array.
364+
// Calculate capacity for array.
371365
let outputs_capacity = circuit_info.values.len();
372-
let u384_struct_layout = layout_repeat(&get_integer_layout(96), 4)?.0;
373-
let outputs_capacity_bytes = layout_repeat(&u384_struct_layout, outputs_capacity)?
374-
.0
375-
.pad_to_align()
376-
.size();
366+
let u384_integer_layout = get_integer_layout(384);
367+
let outputs_layout = layout_repeat(&u384_integer_layout, outputs_capacity)?.0;
368+
let outputs_capacity_bytes = outputs_layout.pad_to_align().size();
377369
let outputs_capacity_bytes_value =
378370
ok_block.const_int(context, location, outputs_capacity_bytes, 64)?;
379371

@@ -387,13 +379,14 @@ fn build_eval<'ctx, 'this>(
387379
location,
388380
)?)?;
389381

382+
// Insert evaluated gates into the array.
390383
for (i, gate) in gates.into_iter().enumerate() {
391384
let value_ptr = ok_block.gep(
392385
context,
393386
location,
394387
outputs_ptr,
395388
&[GepIndex::Const(i as i32)],
396-
build_u384_struct_type(context),
389+
IntegerType::new(context, 384).into(),
397390
)?;
398391
ok_block.store(context, location, value_ptr, gate)?;
399392
}
@@ -464,12 +457,24 @@ fn build_eval<'ctx, 'this>(
464457
Ok(())
465458
}
466459

467-
/// Builds the evaluation of all circuit gates, returning:
468-
/// - An array of two branches, the success block and the error block respectively.
469-
/// - The error block contains the index of the first failure as argument.
470-
/// - A vector of the gate values. In case of failure, not all values are guaranteed to be computed.
460+
/// Receives the circuit inputs, and builds the evaluation of the full circuit.
461+
///
462+
/// Returns two branches. The success block and the error block respectively.
463+
/// - The success block receives nothing.
464+
/// - The error block receives:
465+
/// - The index of the first gate that could not be computed.
466+
///
467+
/// The evaluated gates are returned separately, as a vector of `MLIR` values.
468+
/// Note that in the case of error, not all MLIR values are guaranteed to have been computed,
469+
/// and should not be used carelessly.
471470
///
472-
/// The original Cairo hint evaluates all gates, even in case of failure. This implementation exits on first error, as there is no need for the partial outputs yet.
471+
/// TODO: Consider returning the evaluated gates through the block directly:
472+
/// - As a pointer to a heap allocated array of gates.
473+
/// - As a llvm struct/array of evaluted gates (its size could get really big).
474+
/// - As arguments to the block (one argument per block).
475+
///
476+
/// The original Cairo hint evaluates all gates, even in case of failure.
477+
/// This implementation exits on first error, as there is no need for the partial outputs yet.
473478
fn build_gate_evaluation<'ctx, 'this>(
474479
context: &'this Context,
475480
mut block: &'this Block<'ctx>,
@@ -479,43 +484,44 @@ fn build_gate_evaluation<'ctx, 'this>(
479484
circuit_data: Value<'ctx, 'ctx>,
480485
circuit_modulus: Value<'ctx, 'ctx>,
481486
) -> Result<([&'this Block<'ctx>; 2], Vec<Value<'ctx, 'ctx>>)> {
482-
// Throughout the evaluation of the circuit we maintain an array of known gate values
483-
// Initially, it only contains the inputs of the circuit.
484-
// Unknown values are represented as None
487+
// Each gate is represented as a MLIR value, and identified by an offset in the gate vector.
488+
// - `None` implies that the gate value *has not* been compiled yet.
489+
// - `Some` implies that the gate values *has* already been compiled, and therefore can be safely used.
490+
// Initially, some gate values are already known.
491+
let mut gates = vec![None; 1 + circuit_info.n_inputs + circuit_info.values.len()];
492+
493+
// The first gate always has a value of 1. It is implicity referred by some gate offsets.
494+
gates[0] = Some(block.const_int(context, location, 1, 384)?);
485495

486-
let mut values = vec![None; 1 + circuit_info.n_inputs + circuit_info.values.len()];
487-
values[0] = Some(block.const_int(context, location, 1, 384)?);
496+
// The input gates are also known at the start. We take them from the `circuit_data` array.
497+
let u384_type = IntegerType::new(context, 384).into();
488498
for i in 0..circuit_info.n_inputs {
489499
let value_ptr = block.gep(
490500
context,
491501
location,
492502
circuit_data,
493503
&[GepIndex::Const(i as i32)],
494-
IntegerType::new(context, 384).into(),
504+
u384_type,
495505
)?;
496-
values[i + 1] = Some(block.load(
497-
context,
498-
location,
499-
value_ptr,
500-
IntegerType::new(context, 384).into(),
501-
)?);
506+
gates[i + 1] = Some(block.load(context, location, value_ptr, u384_type)?);
502507
}
503508

504509
let err_block = helper.append_block(Block::new(&[(
505510
IntegerType::new(context, 64).into(),
506511
location,
507512
)]));
513+
let ok_block = helper.append_block(Block::new(&[]));
508514

509515
let mut add_offsets = circuit_info.add_offsets.iter().peekable();
510516
let mut mul_offsets = circuit_info.mul_offsets.iter().enumerate();
511517

512518
// We loop until all gates have been solved
513519
loop {
514520
// We iterate the add gate offsets as long as we can
515-
while let Some(&add_gate_offset) = add_offsets.peek() {
516-
let lhs_value = values[add_gate_offset.lhs].to_owned();
517-
let rhs_value = values[add_gate_offset.rhs].to_owned();
518-
let output_value = values[add_gate_offset.output].to_owned();
521+
while let Some(&gate_offset) = add_offsets.peek() {
522+
let lhs_value = gates[gate_offset.lhs].to_owned();
523+
let rhs_value = gates[gate_offset.rhs].to_owned();
524+
let output_value = gates[gate_offset.output].to_owned();
519525

520526
// Depending on the values known at the time, we can deduce if we are dealing with an ADD gate or a SUB gate.
521527
match (lhs_value, rhs_value, output_value) {
@@ -544,7 +550,7 @@ fn build_gate_evaluation<'ctx, 'this>(
544550
// Truncate back
545551
let value =
546552
block.trunci(value, IntegerType::new(context, 384).into(), location)?;
547-
values[add_gate_offset.output] = Some(value);
553+
gates[gate_offset.output] = Some(value);
548554
}
549555
// SUB: lhs = out - rhs
550556
(None, Some(rhs_value), Some(output_value)) => {
@@ -572,7 +578,7 @@ fn build_gate_evaluation<'ctx, 'this>(
572578
// Truncate back
573579
let value =
574580
block.trunci(value, IntegerType::new(context, 384).into(), location)?;
575-
values[add_gate_offset.lhs] = Some(value);
581+
gates[gate_offset.lhs] = Some(value);
576582
}
577583
// We can't solve this add gate yet, so we break from the loop
578584
_ => break,
@@ -582,12 +588,10 @@ fn build_gate_evaluation<'ctx, 'this>(
582588
}
583589

584590
// If we can't advance any more with add gate offsets, then we solve the next mul gate offset and go back to the start of the loop (solving add gate offsets).
585-
if let Some((gate_offset_idx, &circuit::GateOffsets { lhs, rhs, output })) =
586-
mul_offsets.next()
587-
{
588-
let lhs_value = values[lhs].to_owned();
589-
let rhs_value = values[rhs].to_owned();
590-
let output_value = values[output].to_owned();
591+
if let Some((gate_offset_idx, gate_offset)) = mul_offsets.next() {
592+
let lhs_value = gates[gate_offset.lhs].to_owned();
593+
let rhs_value = gates[gate_offset.rhs].to_owned();
594+
let output_value = gates[gate_offset.output].to_owned();
591595

592596
// Depending on the values known at the time, we can deduce if we are dealing with an MUL gate or a INV gate.
593597
match (lhs_value, rhs_value, output_value) {
@@ -616,7 +620,7 @@ fn build_gate_evaluation<'ctx, 'this>(
616620
// Truncate back
617621
let value =
618622
block.trunci(value, IntegerType::new(context, 384).into(), location)?;
619-
values[output] = Some(value)
623+
gates[gate_offset.output] = Some(value)
620624
}
621625
// INV: lhs = 1 / rhs
622626
(None, Some(rhs_value), Some(_)) => {
@@ -691,7 +695,7 @@ fn build_gate_evaluation<'ctx, 'this>(
691695
let inverse =
692696
block.trunci(inverse, IntegerType::new(context, 384).into(), location)?;
693697

694-
values[lhs] = Some(inverse);
698+
gates[gate_offset.lhs] = Some(inverse);
695699
}
696700
// The imposibility to solve this mul gate offset would render the circuit unsolvable
697701
_ => return Err(SierraAssertError::ImpossibleCircuit.into()),
@@ -702,15 +706,17 @@ fn build_gate_evaluation<'ctx, 'this>(
702706
}
703707
}
704708

709+
block.append_operation(cf::br(ok_block, &[], location));
710+
705711
// Validate all values have been calculated
706712
// Should only fail if the circuit is not solvable (bad form)
707-
let values = values
713+
let evaluated_gates = gates
708714
.into_iter()
709715
.skip(1 + circuit_info.n_inputs)
710716
.collect::<Option<Vec<Value>>>()
711717
.ok_or(SierraAssertError::ImpossibleCircuit)?;
712718

713-
Ok(([block, err_block], values))
719+
Ok(([ok_block, err_block], evaluated_gates))
714720
}
715721

716722
/// Generate MLIR operations for the `circuit_failure_guarantee_verify` libfunc.
@@ -876,6 +882,8 @@ fn build_get_output<'ctx, 'this>(
876882
};
877883
let output_type_id = &info.output_ty;
878884

885+
let u384_type = IntegerType::new(context, 384).into();
886+
879887
let output_offset_idx = *circuit_info
880888
.values
881889
.get(output_type_id)
@@ -885,7 +893,7 @@ fn build_get_output<'ctx, 'this>(
885893

886894
let outputs = entry.arg(0)?;
887895

888-
let values_ptr = entry.extract_value(
896+
let circuit_ptr = entry.extract_value(
889897
context,
890898
location,
891899
outputs,
@@ -900,20 +908,15 @@ fn build_get_output<'ctx, 'this>(
900908
1,
901909
)?;
902910

903-
let output_struct_ptr = entry.gep(
911+
let output_integer_ptr = entry.gep(
904912
context,
905913
location,
906-
values_ptr,
914+
circuit_ptr,
907915
&[GepIndex::Const(output_idx as i32)],
908-
build_u384_struct_type(context),
909-
)?;
910-
911-
let output_struct = entry.load(
912-
context,
913-
location,
914-
output_struct_ptr,
915-
build_u384_struct_type(context),
916+
u384_type,
916917
)?;
918+
let output_integer = entry.load(context, location, output_integer_ptr, u384_type)?;
919+
let output_struct = u384_integer_to_struct(context, entry, location, output_integer)?;
917920

918921
let guarantee_type_id = &info.branch_signatures()[0].vars[1].ty;
919922
let guarantee = build_struct_value(

src/types/circuit.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -278,16 +278,17 @@ pub fn build_circuit_data<'ctx>(
278278
///
279279
/// ## Layout:
280280
///
281-
/// Holds N_VALUES elements, where each element is a u384 struct,
282-
/// A u384 struct contains 4 limbs, each a u96 integer.
281+
/// Holds the evaluated circuit output gates and the circuit modulus.
282+
/// - The data is stored as a dynamic array of u384 integers.
283+
/// - The modulus is stored as a u384 in struct form (multi-limb).
283284
///
284285
/// ```txt
285286
/// type = struct {
286-
/// data: *u384s,
287-
/// modulus: u384s,
287+
/// data: *u384,
288+
/// modulus: u384struct,
288289
/// };
289290
///
290-
/// u384s = struct {
291+
/// u384struct = struct {
291292
/// limb1: u96,
292293
/// limb2: u96,
293294
/// limb3: u96,
@@ -331,15 +332,15 @@ pub fn build_circuit_outputs<'ctx>(
331332
0,
332333
)?;
333334

334-
let u384_struct_layout = layout_repeat(&get_integer_layout(96), 4)?.0;
335+
let u384_integer_layout = get_integer_layout(384);
335336

336337
let new_gates_ptr = build_array_dup(
337338
context,
338339
&entry,
339340
location,
340341
gates_ptr,
341342
circuit.circuit_info.values.len(),
342-
u384_struct_layout,
343+
u384_integer_layout,
343344
)?;
344345

345346
let new_outputs = entry.insert_value(context, location, outputs, new_gates_ptr, 0)?;

0 commit comments

Comments
 (0)