-
Notifications
You must be signed in to change notification settings - Fork 36
Allow support for computed indices #444
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
Conversation
31ceb57 to
2dd6612
Compare
dd7f771 to
6fd045d
Compare
ef562f2 to
33e2e4f
Compare
Fails for now
33e2e4f to
8af9c70
Compare
|
@Leo-Besancon , is this ready? Or would you like an extra round of reviews? |
Hi! This PR is ready on my side, but it needs additional reviews, especially as it touches various parts of the code (parser, AST and MIR).
Then, the v0.5 Milestone would be done and the syntax isn't likely to change much (aside from the multi trace issue). |
bobbinth
left a comment
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.
Looks good! Thank you! This is a very light review from me - I didn't really review the parser changes, and even the MIR part, I reviewed sporadically. I did leave a few questions/comments inline though.
| match value.borrow().deref() { | ||
| Op::Value(Value { | ||
| value: | ||
| SpannedMirValue { | ||
| value: MirValue::Constant(ConstantValue::Felt(c)), | ||
| .. | ||
| }, | ||
| .. | ||
| }) => Some(*c), | ||
| _ => None, | ||
| } |
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 it would be cleaner to add a method on Value for this - something like Value::get_inner_constant(). Then here it would be:
match value.borrow().deref() {
Op::Value(value) => value.get_inner_constant(),
_ => None,
}We can also then re-use this code in the other get_inner_const() function (in constant propagation). In fact, I wonder if it may make sense to combine these two methods.
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.
In fact, I wonder if it may make sense to combine these two methods.
I tried it but it seemed to break the added test (https://github.com/0xMiden/air-script/pull/444/files/88bb70ce0b281ab3593e9675ba7e07e6215f969b#diff-0ac8abbe17a8a361edfcb0a3a06c7108d5c316885eebc406d0669a3b5754a821)
For now Value's impl is reused in both constant propagation and translation from ast to Mir in 6e734dc
| fn apply_mds(state: felt[2]) -> felt[3] { | ||
| return [sum([s * m for (s, m) in (state, mds_row)]) for mds_row in MDS]; | ||
| } |
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 the part that was not possible before, right?
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 may not be the right place, but I imagine this is actually the only place where we'd ever use matrices. For the rare cases where we want to evaluate a matrix (only hash functions I think), we could just manually implement it. If so it might be worth removing matrices entirely. WDYT?
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 the part that was not possible before, right?
Yes when coupled with a function. I think it was ok without a function before because it was computed before semantic analysis.
If so it might be worth removing matrices entirely. WDYT?
I agree, its already in the todo list for the frontend refactor. Ill gather those in a separate issue.
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 the part that was not possible before, right?
Not exactly, the apply_mds function was already tested in https://github.com/0xMiden/air-script/blob/allow-computed-indices-upstream/air-script/tests/list_comprehension/list_comprehension_nested.air
In the list_comprehension_nested test, it is indeed applied to the state as you suggested below, so we can check the rust code.
What is new is to be able to index into the vec_1 and vec_2 list comprehensions with indices whose can only be computed late into the compilation process (for instance, because they rely on list comprehensions)
In this test (computed_indices_complex), I needed a vector that would not be folded as a constant during AST's constant propagation, otherwise I would not be able to test the computed indices targetting list comprehensions properly.
I'll add some comments to make what this test does clearer.
| let state_1 = [1, 1]; | ||
| let state_2 = [2, 0]; | ||
|
|
||
| # [1+2, 2+3, 3+4] | ||
| # [3, 5, 7] | ||
| let vec_1 = apply_mds(state_1); | ||
|
|
||
| # [2, 4, 6] | ||
| let vec_2 = apply_mds(state_2); | ||
|
|
||
| # 2 * 2 - 4 | ||
| let x = double(2) - vec_2[1]; | ||
|
|
||
| # vec_2[0] = 2 | ||
| let y = vec_2[x]; | ||
|
|
||
| # vec_1[2] = 7 | ||
| let z = vec_1[y]; | ||
|
|
||
| enf a[1] = double(z); |
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 would maybe simplify this test and at the same time make it a bit more robust. Specifically:
- Let's use trace columns as the
state. - Maybe we can apply MDS once and then in Rust code we'd see the actual formula of how it is getting applied (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.
I've adapted the air-script of the test in 82cd993 but it does not compile as is, I'm investigating why that is.
It seems we extract the constant from the wrong value, extracting the vec_1 instead of 1 or report the error incorrectly here:
| let x = double(2) - vec_2[1]; |
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 would maybe simplify this test and at the same time make it a bit more robust. Specifically:
* Let's use trace columns as the `state`. * Maybe we can apply MDS once and then in Rust code we'd see the actual formula of how it is getting applied (I think).
In the latest commit, I've made it so that one MDS is kept on a constant vector to be able to produce constant indices, and use it to index into the second MDS that does rely on the state.
I've also added comments on what is expected / happening in the test, please tell me if it's better!
mir/src/passes/mod.rs
Outdated
| /// stage. | ||
| pub fn handle_accessor_visit( | ||
| accessor: Link<Op>, | ||
| compute_indices: bool, |
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 (feel free to ignore): I would maybe invert compute_indices into something like expect_constant_indices. May make the meaning a bit more clear (at least that's how we describe it in the doc comments).
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.
parser/src/ast/trace.rs
Outdated
| /// The access type associated to this TraceBinding | ||
| pub access: Option<AccessType>, |
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.
What this filed mean here? That is, in what situations would a trace binding have access vs. when it could be None?
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.
Im not sure why it is this way or if it could be without the Option, so I'm only interpreting here based on the code.
It seems it allows setting it after creation, potentially handling the difference between an unset access_type and AccessType::Default, distinguishing between an error (a mismatch) and a missing access_type.
Noting so we can revisit with @Leo-Besancon based on his feedback
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.
What this filed mean here? That is, in what situations would a trace binding have
accessvs. when it could beNone?
Indeed the Option is unnecessary as the None case can just be handled the same as the Default case. I've removed it in the latest commit
parser/src/ast/trace.rs
Outdated
| Some(AccessType::Default) => self.ty, | ||
| Some(AccessType::Slice(range_expr)) => Type::Vector(range_expr.to_slice_range().len()), | ||
| Some(AccessType::Index(_)) => Type::Felt, | ||
| Some(AccessType::Matrix(..)) => Type::Felt, |
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.
Under what circumstances could TraceBinding have type Matrix? Should this be unreachable!?
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.
Made it unreachable in abbf525
Al-Kindi-0
left a comment
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.
LGTM (given my understanding of this part of the codebase)
mir/src/passes/mod.rs
Outdated
| } | ||
|
|
||
| /// Handle the visit of an accessor node, used for both Unrolling and ConstantPropagation passes | ||
| /// The `compute_indices` bool indicates whether we the indices need to be known constant at 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.
this needs rewording
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.
Reworded in c8adbf5, let me know if it needs more adjustments.
rename handle_accessor_visit arg: compute_indices -> expect_constant_indices
9d75de1 to
c8adbf5
Compare
|
@Leo-Besancon , is this good to go? |
Hi! Yes, if you guys are happy with our latest replies, it should be good to merge, I think we've addressed everything! |
|
Thank you! |
This PR closes #245
Task list:
AccessType::Index(usize)byAccessType::Index(Box<ScalarExpr>)in parserNote: This PR contains a fix for Mir's Inlining that has been raised by the
computed_indices_complex.rsE2E test. Because of this fix, we need to call the Inlining multiple times (until there are no moreCallnodes in the graph). Maybe there is a better way to handle this (for instance, keeping track when we duplicate a Call node and add it to theworking_stack), but I haven't thought too much about it for now.Question: @adr1anh @bobbinth Should we also handle computed slice bounds? For now, slices are assumed constant (either an integer or a reference to a constant symbol) and are resolved early. I still handle them in the MIR, but the are constant by that point, so I should refactor the code to either ignore slices in the MIR, or handle them fully as computed expressions.
It may look something like this (though it may not be the best example to showcase usage..):