-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Codegen option for generic constraint evaluation format #104
base: next
Are you sure you want to change the base?
Conversation
One quick comment: this should go into its own crate (not winterfell codegen). So, instead of being under |
I think |
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 a partial review. I left several preliminary comments inline related to refactoring. After that, it should be easier to assess some of the other design decisions here.
air-script/src/cli/transpile.rs
Outdated
generate_constraint_evaluation(&ir, 2).unwrap(); | ||
|
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.
A couple things here -
- In general, it would be better to use
expect
thanunwrap
in production code. - Now that we have multiple codegen options, we should update the CLI to accept another input that specifies the target output type. This should be done in a separate PR, though. Can you create an issue for this and add it to the "nice to have" list for Tracking issue: core functionality for Miden VM constraints #75?
Based on those 2 points of feedback, I think we should remove this from here.
codegen/gce/Cargo.toml
Outdated
[package] | ||
name = "air-codegen-gce" | ||
version = "0.1.0" | ||
description="Codegen for generic constraint evaluation" |
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.
nit: I would adjust this to "Code generation for the generic constraint evaluation format."
/// Holds data for JSON generation | ||
#[derive(Default, Debug)] | ||
pub struct Evaluation { | ||
num_polys: u8, |
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 think we should probably use u16
here. Although u8
is right for Miden, other VMs may need more (e.g. zk EVMs).
// constants | ||
//---------- |
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.
nit: let's use the same format for separators that we follow elsewhere in the codebase and in the VM.
// expressions (boundary constraints) | ||
//------------ | ||
for main_constraints in &boundary_constraints_vec[..2] { | ||
for main_constraint in *main_constraints { | ||
handle_boundary_operation( | ||
ir, | ||
main_constraint, | ||
&mut expressions, | ||
&mut output, | ||
&constants, | ||
&const_public_type_map, | ||
)?; | ||
} | ||
} | ||
for aux_constraints in &boundary_constraints_vec[2..] { | ||
for aux_constraint in *aux_constraints { | ||
handle_boundary_operation( | ||
ir, | ||
&( | ||
aux_constraint.0 + ir.num_polys().0 as usize, | ||
aux_constraint.1, | ||
), | ||
&mut expressions, | ||
&mut output, | ||
&constants, | ||
&const_public_type_map, | ||
)?; | ||
} | ||
} |
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.
These loops are almost exact duplicates. Can you pull this out into a helper function?
codegen/gce/src/tests/mod.rs
Outdated
#[test] | ||
fn constraint_evaluation() { | ||
let generated_air = get_ir("src/tests/constraint_evaluation.air".to_string()).unwrap(); | ||
let evaluation = Evaluation::new(&generated_air, 2).expect("Evaluation creation failed"); | ||
evaluation | ||
.generate_evaluation_file() | ||
.expect("File generation failed"); | ||
} |
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.
Thanks for adding a test! Since this is a test of the full transpilation from parser to IR to codegen, I think it doesn't belong in this crate. This crate should contain tests that are only for gce
codegen.
Instead, it would be great to update the existing transpilation tests in the air-script
crate so that they test for both Winterfell and gce
. I would do this as follows:
1. Update the Test
struct to something like the following in air-script/tests/helpers.rs`
pub struct Test {
source: String,
parsed: Source,
ir: AirIR
}
impl Test {
pub fn new(input_path: String) -> Self {
// load source input from file
let source = fs::read_to_string(&self.input_path).map_err(|err| {
TestError::IO(format!(
"Failed to open input file `{:?}` - {}",
input_path, err
))
})?;
// parse the input file to the internal representation
let parsed = parse(source.as_str()).map_err(|_| {
TestError::Parse(format!(
"Failed to parse the input air file at {}",
input_path
))
})?;
let ir = AirIR::from_source(&parsed).map_err(|_| {
TestError::IR(format!(
"Failed to convert the input air file at {} to IR representation",
input_path
))
})?;
Test { source, parsed, ir }
}
pub fn generate_winterfell(&self) -> Result<String, TestError> {
// generate Rust code targeting Winterfell
let codegen = WinterfellCodeGenerator::new(&self.ir);
Ok(codegen.generate())
}
pub fn generate_gce(&self) -> Result<String, TestError> {
// generate Rust code targeting Winterfell
let codegen = GCECodeGenerator::new(&self.ir);
Ok(codegen.generate())
}
}
2. Update the tests to do code generation for both types in air-script/tests/main.rs
, for example:
#[test]
fn winterfell_aux_trace() {
// Winterfell target
let generated_air = Test::new("tests/aux_trace/aux_trace.air".to_string())
.generate_winterfell()
.unwrap();
let expected = expect_file!["aux_trace/aux_trace.rs"];
expected.assert_eq(&generated_air);
}
#[test]
fn gce_aux_trace() {
// GCE target
let generated_air = Test::new("tests/aux_trace/aux_trace.air".to_string())
.generate_gce()
.unwrap();
let expected = expect_file!["aux_trace/aux_trace.json"];
expected.assert_eq(&generated_air);
}
codegen/winterfell/Cargo.toml
Outdated
|
||
[dev-dependencies] | ||
parser = { package = "air-parser", path = "../../parser", version = "0.1.0" } |
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 is this necessary?
ir/src/lib.rs
Outdated
// set the overall trace columns count | ||
num_polys = (columns.main_cols.len() as u8, columns.aux_cols.len() as u8); |
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 won't quite work, because we could have more than one TraceCols
source section (e.g. once we support modules).
Instead of doing it here and initializing above, maybe we should collect this information inside the symbol table, the way that we collect the num_trace_segments
information. Then you could update the into_declarations
method to pass the information you need to initialize num_polys
. I think this will encapsulate things a bit more cleanly.
ir/src/lib.rs
Outdated
// I don't think that adding a new field just for generic constraint evaluation worth it. | ||
// TODO: get rid of this field | ||
// amount of named columns: (main, aux) | ||
num_polys: (u8, u8), |
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 like that you're considering whether or not we want this, but I actually do think we need to add something here, because I don't think there's any other way to get this information.
One note though - currently we only have 2 trace segments, but we've actually been making the IR more generic to support more than 2 segments, so this should probably be a vector to be consistent with those changes.
2d30954
to
0a66ed6
Compare
0a66ed6
to
02dba70
Compare
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.
Thanks @Overcastan! This refactor looks a lot cleaner. I've done a partial review. Can you please address these comments and add doc comments to functions where they're missing? It'll make it easier to review the rest of this.
air-script/tests/helpers.rs
Outdated
} | ||
|
||
pub fn generate_gce(&self, extension_degree: u8, path: &str) -> Result<(), TestError> { | ||
// generate Rust code targeting Winterfell |
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.
nit: this comment is incorrect.
codegen.generate() | ||
} | ||
|
||
pub fn generate_gce(&self, extension_degree: u8, path: &str) -> Result<(), TestError> { |
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.
Can you add a doc comment to this function and the generate_winterfell
function?
air-script/tests/helpers.rs
Outdated
pub fn new(input_path: String) -> Self { | ||
Test { input_path } | ||
} | ||
|
||
pub fn transpile(&self) -> Result<String, TestError> { | ||
pub fn new(input_path: String) -> Result<Self, TestError> { |
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'd prefer to keep these separate. The idea is that if they're separate then in the future we could offer different options - full transpilation via transpile
, or only testing the parsing
or ir
generation steps
air-script/tests/helpers.rs
Outdated
use codegen_winter::CodeGenerator; | ||
use codegen_gce::GCECodeGenerator; | ||
use codegen_winter::WinterfellCodeGenerator; |
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.
Once we have both of these in the air_script
library, you can import as:
use air_script::{GceCodeGenerator, WinterfellCodeGenerator};
I think we should actually do the same with the IR and parser inputs, so we would have:
use air_script::{parse, AirIR, GceCodeGenerator, WinterfellCodeGenerator};
air-script/src/lib.rs
Outdated
pub use codegen_winter::CodeGenerator; | ||
pub use codegen_winter::WinterfellCodeGenerator; |
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.
We should also export the GceCodeGenerator
.
Also, I'd actually prefer to name these as CodeGenerator
in their respective crates and then alias the imports, i.e.:
pub use codegen_gce::CodeGenerator as GceCodeGenerator;
pub use codegen_winter::CodeGenerator as WinterfellCodeGenerator;
air-script/tests/main.rs
Outdated
use expect_test::expect_file; | ||
use std::fs::File; | ||
use std::io::prelude::*; |
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 don't think we need to change the ordering and spacing here. These three imports are all external imports. What was your reasoning for this?
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.
Fixed. There was no reason, it looks like the auto import just arranged them like this, and I overlooked
air-script/tests/main.rs
Outdated
fn gce_aux_trace() { | ||
Test::new("tests/aux_trace/aux_trace.air".to_string()) | ||
.unwrap() | ||
.generate_gce(2, "tests/aux_trace/generated_aux_trace.json") |
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.
Let's not unwrap twice in the same chain here. Instead, we can do something like this.
let test = Test::new("tests/aux_trace/aux_trace.air".to_string()).unwrap();
assert!(test
.generate_gce(2, "tests/aux_trace/generated_aux_trace.json")
.is_ok());
air-script/tests/main.rs
Outdated
let mut file = File::open("tests/aux_trace/generated_aux_trace.json").unwrap(); | ||
let mut contents = String::new(); | ||
file.read_to_string(&mut contents).unwrap(); | ||
|
||
expected.assert_eq(&contents); | ||
} |
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 generated file should be deleted at the end of the test, not checked into the repo. In general, think of tests as having 3 stages: "setup", "execute", "tear down", where the tear down step leaves the system as you found it.
- Use a variable for "tests/aux_trace/generated_aux_trace.json" instead of typing it twice
In general, I would probably tidy up the handling of the paths a bit too, so I would combine all of these suggestions into something like:
#[test]
fn gce_aux_trace() {
let test_path = "tests/aux_trace/aux_trace";
let result_file = "tests/aux_trace/generated_aux_trace.json";
let expected = expect_file![[test_path, "json"].join(".")];
let test = Test::new([test_path, "air"].join(".")).unwrap();
assert!(test.generate_gce(2, result_file).is_ok());
let mut file = File::open(result_file).unwrap();
let mut result = String::new();
file.read_to_string(&mut result).unwrap();
expected.assert_eq(&result);
// delete the generated result file
remove_file(result_file).unwrap();
}
codegen/gce/src/lib.rs
Outdated
use error::ConstraintEvaluationError; | ||
|
||
mod helpers; | ||
use helpers::{acumulate_constants, count_boundary_rand_values, Expression, NodeType}; |
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.
nit: there's a typo here - this should be accumulate_constants
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.
Another partial review :) The testing structure looks good now except for one nit inline. The test reference code is incorrect, though. I would start by thinking through what the reference json should be to make sure it's clear. I left a few examples inline, but please let me know if you have questions
air-script/tests/main.rs
Outdated
let mut contents = String::new(); | ||
file.read_to_string(&mut contents) | ||
.expect("Failed to read form file"); | ||
fs::remove_file(result_file).expect("Failed to remove file"); |
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.
nit: since this file removal is a "tear down" step, it should be at the end of the test, after the assertion that constitutes the actual test. That will be true for all of the tests in this file.
air-script/tests/main.rs
Outdated
#[test] | ||
#[ignore] // exponentiation for boundary constraints is not ready | ||
fn gce_exponentiation() { | ||
let test_path = "tests/exponentiation/exponentiation"; | ||
let result_file = "tests/exponentiation/generated_exponentiation.json"; | ||
|
||
let test = Test::new([test_path, "air"].join(".")); | ||
test.generate_gce(2, result_file) | ||
.expect("GCE generation failed"); | ||
|
||
let expected = expect_file![[test_path, "json"].join(".").trim_start_matches("tests/")]; | ||
|
||
let mut file = File::open(result_file).expect("Failed to open file"); | ||
let mut contents = String::new(); | ||
file.read_to_string(&mut contents) | ||
.expect("Failed to read form file"); | ||
fs::remove_file(result_file).expect("Failed to remove file"); | ||
|
||
expected.assert_eq(&contents); | ||
} | ||
|
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.
Since this isn't done, can we move this test (and the related files) to a separate branch? That way they can be handled in a separate PR in the future and we can keep this review focused on things that are ready
codegen/gce/src/helpers.rs
Outdated
// I think we can allow non camel case type since we translate it directly to string in node | ||
// reference type, where we don't use camel case | ||
/// Stroes node type required in [NodeReference] struct | ||
#[allow(non_camel_case_types, clippy::upper_case_acronyms)] | ||
#[derive(Debug, Clone)] | ||
pub enum NodeType { | ||
POL, | ||
POL_NEXT, | ||
VAR, | ||
CONST, | ||
EXPR, | ||
} |
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.
Your argument is reasonable, but I would probably stick with Rust conventions and then just implement Display
for NodeType
as well. In general, I prefer not to make exceptions to conventions unless there's a really good reason for it. In this case, I think it's pretty much neutral, so I'd lean in favor of the convention
codegen/gce/src/helpers.rs
Outdated
// TODO: change String to &str (Or should I create another enum?) | ||
/// Stores data used in JSON generation | ||
#[derive(Debug)] | ||
pub struct Expression { | ||
pub op: String, | ||
pub lhs: NodeReference, | ||
pub rhs: NodeReference, | ||
} |
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.
Yep, I would create another enum
codegen/gce/src/helpers.rs
Outdated
/// Counts overall number of random values in boundary constraints | ||
pub fn count_boundary_rand_values<'a>( | ||
expr: &'a BoundaryExpr, | ||
max_random_values_index: &'a mut usize, | ||
) { | ||
use BoundaryExpr::*; | ||
match expr { | ||
Rand(i) => *max_random_values_index = *max_random_values_index.max(&mut (*i + 1)), | ||
Add(l, r) | Sub(l, r) | Mul(l, r) => { | ||
count_boundary_rand_values(l, max_random_values_index); | ||
count_boundary_rand_values(r, max_random_values_index); | ||
} | ||
_ => {} | ||
} | ||
} | ||
|
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.
Instead of having this "helpers" file, I would put the structs (and related implementations) into the lib
file and move these helper functions to a utils.rs
file
"expressions": [ | ||
{"op": "ADD", "lhs": {"type": "POL", "index": 1}, "rhs": {"type": "POL", "index": 2}}, | ||
{"op": "SUB", "lhs": {"type": "POL_NEXT", "index": 0}, "rhs": {"type": "EXPR", "index": 0}}, | ||
{"op": "ADD", "lhs": {"type": "POL", "index": 2}, "rhs": {"type": "POL_NEXT", "index": 0}}, | ||
{"op": "SUB", "lhs": {"type": "POL_NEXT", "index": 1}, "rhs": {"type": "EXPR", "index": 2}}, | ||
{"op": "ADD", "lhs": {"type": "POL", "index": 0}, "rhs": {"type": "POL", "index": 1}}, | ||
{"op": "SUB", "lhs": {"type": "POL", "index": 2}, "rhs": {"type": "EXPR", "index": 4}}, | ||
{"op": "ADD", "lhs": {"type": "POL", "index": 0}, "rhs": {"type": "VAR", "index": 16}}, | ||
{"op": "ADD", "lhs": {"type": "EXPR", "index": 6}, "rhs": {"type": "POL", "index": 1}}, | ||
{"op": "ADD", "lhs": {"type": "EXPR", "index": 7}, "rhs": {"type": "VAR", "index": 17}}, | ||
{"op": "MUL", "lhs": {"type": "POL", "index": 3}, "rhs": {"type": "EXPR", "index": 8}}, | ||
{"op": "SUB", "lhs": {"type": "POL_NEXT", "index": 3}, "rhs": {"type": "EXPR", "index": 9}}, | ||
{"op": "ADD", "lhs": {"type": "POL", "index": 2}, "rhs": {"type": "VAR", "index": 16}}, | ||
{"op": "MUL", "lhs": {"type": "POL_NEXT", "index": 4}, "rhs": {"type": "EXPR", "index": 11}}, | ||
{"op": "SUB", "lhs": {"type": "POL", "index": 4}, "rhs": {"type": "EXPR", "index": 12}}, | ||
{"op": "SUB", "lhs": {"type": "POL", "index": 0}, "rhs": {"type": "CONST", "index": 0}}, | ||
{"op": "SUB", "lhs": {"type": "POL", "index": 1}, "rhs": {"type": "CONST", "index": 0}}, | ||
{"op": "SUB", "lhs": {"type": "POL", "index": 3}, "rhs": {"type": "CONST", "index": 0}}, | ||
{"op": "SUB", "lhs": {"type": "POL", "index": 4}, "rhs": {"type": "VAR", "index": 16}}, | ||
{"op": "SUB", "lhs": {"type": "POL", "index": 3}, "rhs": {"type": "CONST", "index": 0}}, | ||
{"op": "SUB", "lhs": {"type": "POL", "index": 4}, "rhs": {"type": "CONST", "index": 0}} | ||
], | ||
"outputs": [1, 3, 5, 10, 13, 14, 15, 16, 17, 18, 19] |
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 output isn't correct, since the "outputs" should be an array of node references.
So for example, the first transition constraint enf a' = b + c
would be expressed by the following:
"expressions": [
{"op": "ADD", "lhs": {"type": "POL", "index": 1}, "rhs": {"type": "POL", "index": 2}},
{"op": "SUB", "lhs": {"type": "POL_NEXT", "index": 0}, "rhs": {"type": "REF", "index": 0}}
],
"outputs": [
{"type": "POL_NEXT", "index": 0},
{"type": "POL", "index": 1},
{"type": "POL", "index": 2},
{"type": "CONST", "index": 0},
{"type": "REF", "index": 0}
]
In this case, the first item in the expressions array represents b+c
, which is then added to outputs as a REF
. The second item in the expressions array represents a' - (b+c)
codegen/gce/src/lib.rs
Outdated
// TODO #2: currently I add all found constants in the vector. Should I add only unique ones, | ||
// since I'll get constants by their value, not index? | ||
let constants = set_constants(ir, &mut const_public_type_map, boundary_constraints_vec); |
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'm not sure if I understood this question correctly, but we should be referencing constants by index, so yes, it would be best to only add unique ones.
In other words, if we had a'= a + 1
, we would have something like the following:
...
"constants": [1]
"expressions": [
{"op":"ADD", "lhs": {"type": "POL", "index": 0}, "rhs": {"type": "CONST", "index": 0}},
{"op": "SUB", "lhs": {"type": "POL_NEXT", "index": 0}, "rhs": {"type": "REF", "index": 0}}
],
"outputs": [
{"type": "POL_NEXT", "index": 0},
{"type": "POL", "index": 0},
{"type": "CONST", "index": 0},
{"type": "REF", "index": 0}
]
f43e251
to
bae4fcc
Compare
2a55bc6
to
4ee1a76
Compare
4ee1a76
to
7a55422
Compare
7a55422
to
fa144de
Compare
1b1afaf
to
644b6b0
Compare
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.
@Overcastan thank you! The refactor you've done looks good. It's much easier to follow. This is a partial review with a few nits and a small suggested adjustment to the pattern you're using to build the expressions & outputs
mod helpers; | ||
use helpers::Test; |
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.
nit: let's put a blank line between the module declaration and the imports
codegen/gce/Cargo.toml
Outdated
@@ -0,0 +1,17 @@ | |||
[package] | |||
name = "air-codegen-gce" | |||
version = "0.1.0" |
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.
Let's set this as 0.2.0
so all crates have the same version
codegen/gce/src/error.rs
Outdated
|
||
pub fn invalid_constant_type(name: &str, constant_type: &str) -> Self { | ||
ConstraintEvaluationError::InvalidConstantType(format!( | ||
"Invalid type of constant \"{name}\". {constant_type} exprected." |
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.
nit: typo in "expected". Let's also reorder to the following: "Invalid type of constant "{name}". Expected "{constant_type}"."
codegen/gce/src/error.rs
Outdated
|
||
pub fn operation_not_found(index: usize) -> Self { | ||
ConstraintEvaluationError::OperationNotFound(format!( | ||
"Operation with index {index} does not match the expression in the expressions JSON array" |
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.
nit: let's adjust this to: "Operation with index {index} does not match the expression in the JSON expressions array."
codegen/gce/src/error.rs
Outdated
impl ConstraintEvaluationError { | ||
pub fn invalid_trace_segment(segment: u8) -> Self { | ||
ConstraintEvaluationError::InvalidTraceSegment(format!( | ||
"Trace segment {segment} is invalid" | ||
)) | ||
} | ||
|
||
pub fn constant_not_found(name: &str) -> Self { | ||
ConstraintEvaluationError::ConstantNotFound(format!("Constant \"{name}\" not found")) | ||
} |
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.
nit: for consistency, let's add "." at the end of the error message sentences
codegen/gce/src/lib.rs
Outdated
// maps indexes in Node vector in AlgebraicGraph and in `expressions` JSON array | ||
let mut expressions_map = BTreeMap::new(); |
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 isn't used at this level, so it can be removed here and created directly when the ExpressionsHandler
is instantiated
codegen/gce/src/lib.rs
Outdated
let mut expressions_handler = ExpressionsHandler::new(ir, &constants, &mut expressions_map); | ||
|
||
let mut expressions = expressions_handler.get_expressions()?; | ||
// vector of `expressions` indexes | ||
let outputs = expressions_handler.get_outputs(&mut expressions)?; |
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.
Instead of this pattern where you get the expressions and then pass them back in, I would keep them in the ExpressionsHandler
. I suggest adjusting to a builder pattern. We could rename ExpressionsHandler
to GceBuilder
, add a method which handles building the expressions
and outputs
, and then add an into
method that consumes the builder and yields the expressions
and outputs
. (This would also reflect the pattern that we'll be moving the IR to in #150)
Something like:
pub fn new(ir: &AirIR, extension_degree: u8) -> Result<Self, ConstraintEvaluationError> {
let num_polys = set_num_polys(ir, extension_degree);
let num_variables = set_num_variables(ir);
let constants = set_constants(ir);
let gce_builder = GceBuilder::new();
gce_builder.build(ir, &constants)?;
let (expressions, outputs) = gce_builder.into_gce();
Ok(CodeGenerator {
num_polys,
num_variables,
constants,
expressions,
outputs,
})
}
codegen/gce/src/expressions.rs
Outdated
pub struct ExpressionsHandler<'a> { | ||
ir: &'a AirIR, | ||
constants: &'a [u64], | ||
// maps indexes in Node vector in AlgebraicGraph and in `expressions` JSON array | ||
expressions_map: &'a mut BTreeMap<usize, usize>, | ||
} |
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.
As noted, the expressions_map
can be instantiated in the new
method, so it no longer needs to be marked as a mut borrow with a lifetime.
I think we should also add the following. As mentioned, we can declare them in new
and then populate them in a build
method.
expressions: Vec<ExpressionJson>,
outputs: Vec<usize>,
I would also probably remove the AirIR
and constants
borrows here and just pass them to the build
method.
For the build
method, we could do something like the following:
pub fn build(
&mut self,
ir: &AirIR,
constants: &[u64],
) -> Result<(), ConstraintEvaluationError> {
self.build_expressions(ir, constants)?;
self.build_outputs(ir)?;
Ok(())
}
codegen/gce/src/expressions.rs
Outdated
/// Parses expressions in transition graph's Node vector, creates [Expression] instances and pushes | ||
/// them to the `expressions` vector. | ||
pub fn get_expressions(&mut self) -> Result<Vec<ExpressionJson>, ConstraintEvaluationError> { |
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.
We can change the signature to the following to use the builder pattern I suggested:
fn build_expressions(
&mut self,
ir: &AirIR,
constants: &[u64],
) -> Result<(), ConstraintEvaluationError> {
codegen/gce/src/expressions.rs
Outdated
/// Fills the `outputs` vector with indexes from `expressions` vector according to the `expressions_map`. | ||
pub fn get_outputs( | ||
&self, | ||
expressions: &mut Vec<ExpressionJson>, | ||
) -> Result<Vec<usize>, ConstraintEvaluationError> { |
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.
We can change the signature to the following to implement the builder pattern I suggested:
fn build_outputs(&self, ir: &AirIR) -> Result<Vec<usize>, ConstraintEvaluationError> {
f72569a
to
bda7dc5
Compare
bda7dc5
to
e839cfa
Compare
Related issue: #56
Things left to implement:
expressions
andoutputs
arrays)Things to implement in later version: