-
Notifications
You must be signed in to change notification settings - Fork 16
feat!: Log Directive support for traces #105
Changes from 2 commits
980e2cc
eddc64a
f7b98fa
a7227ee
def272e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
use std::{cmp::Ordering, collections::BTreeMap}; | ||
|
||
use acir::{ | ||
circuit::directives::{Directive, LogInfo}, | ||
circuit::directives::{Directive, LogOutputInfo}, | ||
native_types::Witness, | ||
FieldElement, | ||
}; | ||
|
@@ -15,13 +15,13 @@ use super::{get_value, insert_value, sorting::route, witness_to_value}; | |
pub fn solve_directives( | ||
initial_witness: &mut BTreeMap<Witness, FieldElement>, | ||
directive: &Directive, | ||
) -> Result<(), OpcodeResolutionError> { | ||
) -> Result<Option<Directive>, OpcodeResolutionError> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the solve should return an optional directive. The directive is either solved or not. If it is solved, then you can get the witness values from the witness map. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed - If a directive is not solved, then it means we did not find a witness that it needed, this will return an Error There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was unsure on whether I should do this but I went with returning a directive mainly to remove duplication of code as the logging information needed by whomever is calling solve is very similar to the Log directive itself. Whomever is calling solve must check that we have a finalized string output and whether the log is a trace or a println. This can be seen in this draft PR: noir-lang/noir#872. I will look into changing this as I agree it confuses the idea of whether the directive has been solved or not. |
||
match directive { | ||
Directive::Invert { x, result } => { | ||
let val = witness_to_value(initial_witness, *x)?; | ||
let inverse = val.inverse(); | ||
initial_witness.insert(*result, inverse); | ||
Ok(()) | ||
Ok(None) | ||
} | ||
Directive::Quotient { a, b, q, r, predicate } => { | ||
let val_a = get_value(a, initial_witness)?; | ||
|
@@ -54,7 +54,7 @@ pub fn solve_directives( | |
initial_witness, | ||
)?; | ||
|
||
Ok(()) | ||
Ok(None) | ||
} | ||
Directive::Truncate { a, b, c, bit_size } => { | ||
let val_a = get_value(a, initial_witness)?; | ||
|
@@ -76,7 +76,7 @@ pub fn solve_directives( | |
initial_witness, | ||
)?; | ||
|
||
Ok(()) | ||
Ok(None) | ||
} | ||
Directive::ToRadix { a, b, radix, is_little_endian } => { | ||
let value_a = get_value(a, initial_witness)?; | ||
|
@@ -127,7 +127,7 @@ pub fn solve_directives( | |
} | ||
} | ||
|
||
Ok(()) | ||
Ok(None) | ||
} | ||
Directive::OddRange { a, b, r, bit_size } => { | ||
let val_a = witness_to_value(initial_witness, *a)?; | ||
|
@@ -153,7 +153,7 @@ pub fn solve_directives( | |
initial_witness, | ||
)?; | ||
|
||
Ok(()) | ||
Ok(None) | ||
} | ||
Directive::PermutationSort { inputs: a, tuple, bits, sort_by } => { | ||
let mut val_a = Vec::new(); | ||
|
@@ -186,23 +186,27 @@ pub fn solve_directives( | |
let value = if value { FieldElement::one() } else { FieldElement::zero() }; | ||
insert_witness(*w, value, initial_witness)?; | ||
} | ||
Ok(()) | ||
Ok(None) | ||
} | ||
Directive::Log(info) => { | ||
let witnesses = match info { | ||
LogInfo::FinalizedOutput(output_string) => { | ||
println!("{output_string}"); | ||
return Ok(()); | ||
Directive::Log { is_trace, output_info } => { | ||
let witnesses = match output_info { | ||
LogOutputInfo::FinalizedOutput(_) => { | ||
return Ok(Some(directive.clone())); | ||
} | ||
LogInfo::WitnessOutput(witnesses) => witnesses, | ||
LogOutputInfo::WitnessOutput(witnesses) => witnesses, | ||
}; | ||
|
||
if witnesses.len() == 1 { | ||
dbg!(witnesses.clone()); | ||
TomAFrench marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let witness = &witnesses[0]; | ||
let log_value = witness_to_value(initial_witness, *witness)?; | ||
println!("{}", format_field_string(*log_value)); | ||
|
||
return Ok(()); | ||
let solved_log_directive = Directive::Log { | ||
is_trace: *is_trace, | ||
output_info: LogOutputInfo::FinalizedOutput(format_field_string(*log_value)), | ||
}; | ||
return Ok(Some(solved_log_directive)); | ||
} | ||
|
||
// If multiple witnesses are to be fetched for a log directive, | ||
|
@@ -221,9 +225,11 @@ pub fn solve_directives( | |
|
||
let output_witnesses_string = "[".to_owned() + &comma_separated_elements + "]"; | ||
|
||
println!("{output_witnesses_string}"); | ||
|
||
Ok(()) | ||
let solved_log_directive = Directive::Log { | ||
is_trace: *is_trace, | ||
output_info: LogOutputInfo::FinalizedOutput(output_witnesses_string), | ||
}; | ||
Ok(Some(solved_log_directive)) | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have two separate process, and not just one which basically notifies when the 'log witness' are solved? Then the caller can decide to print to standard output or to further manipulate them. This logic should not be in ACIR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is needed as without this flag the caller does not know whether to print to standard output or to further manipulate the logs. I was trying to have the same directive for the builtins
std::println
andstd::trace
. For example, ifsolve
simply returns a finalized string to be logged, but bothstd::println
andstd::trace
exist, the caller will not know whether to print to std out or manipulate the trace how it sees fit without more info. The flag would be set during the evaluator, and essentially passed along untilsolve
is called. Only the caller ofsolve
would implement the logic of how to handle a log according to the flag.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not suggesting to remove the flag, sorry for the mis-understanding, the location of my comment is confusing. This is because my first reaction was to ask for a clarification on the flag meaning. But at the end, I think it is better to not handle it in the solve, and just solve the witness.
With the flag (from the directive), the caller should be able to handle it correctly, shouldn't he?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that is the case. We only pass the flag along with the solved witness info so that the caller can determine how they should handle the solved log directive. The process in
solve_directives
forDirective::Log
remains the same for both traces and printlns.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The flag is also used during the solve, that's why I thought we were implementing its logic.
The solve is handling the flag and also behaves differently when there is only one witness. Could you explain why we have these 2 special cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So currently we only support logs of individual fields, arrays, or strings (formatting and handling of complex types can come in a separate PR). In the case of one witness we don't have to construct a list of hex elements. We can simply format the respective witness value and return that the log has been solved. In the case of multiple witnesses this means we have an array, where we have to fetch a list of hex elements, and format them into one finalized string output. Both cases will form a
LogOutputInfo::FinalizedOutput(String)
, which the caller ofsolve
will then handle. In the case of a string this is formatted during acir gen and should already be aLogOutputInfo::FinalizedOutput
. IfDirective::Log
already has finalized output we can just return the string stored in the enum.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the explanation. For me you are processing the logs here and you should not. The Log directive should just hold the parameters (for now just the flag), and the witness to log.
When the gate is solved, i.e when all the witness are known, you can send the parameters and witness values to the caller. That's it. How/where to print, which language to use, etc... all this should be handled by the caller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, I see now. Ok I will refactor this