-
Notifications
You must be signed in to change notification settings - Fork 185
Always use a normal segment for first SegmentArena segment #1845
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
Always use a normal segment for first SegmentArena segment #1845
Conversation
Hi @notlesh |
I've wanted to produce a better way to test this, but it's complicated because the failure occurs in a different repo. One idea for a test case is to show that the dictionaries do not have a temporary segment as their first segment prior to the This should act as a regression test (reproducing the problem and demonstrating that it's fixed) but it doesn't reproduce the exact error. Let me know what you think and if you want more details or want to know how you can reproduce the exact error yourself. |
What do you mean by it Also, there are merge conflicts that need to let me approve the CI run, can you solve them? |
Sorry for the long delay, I'd like to push this forward soon (resolve conflicts and add a test). In the meantime, I was wondering if you could point me to an example of the sort of test you have in mind, as I'm unsure of where to start with this. |
Hi, @notlesh! To test this, I think it would be helpful to know what's the code that's being problematic to see why is this scenario difficult to reproduce, because I'm not sure if there's a Cairo program that can be used for testing because the hint that calls |
Hey, @gabrielbosio , thanks for taking a look. The code that is failing is the first link mentioned in the description, specifically the hint that calls // Helper function for validate_segment_arena.
func _verify_continuity(infos: SegmentInfo*, n_segments_minus_one: felt) {
if (n_segments_minus_one == 0) {
// If there is only one segment left, there is no need to check anything.
return ();
}
// Enforce an empty cell between two consecutive segments so that the start of a segment
// is strictly bigger than the end of the previous segment.
// This is required for proving the soundness of this construction, in the case where a segment
// has length zero.
// Note: the following code was copied from relocate_segment() for efficiency reasons.
let src_ptr = infos[1].start;
let dest_ptr = infos[0].end + 1;
%{ memory.add_relocation_rule(src_ptr=ids.src_ptr, dest_ptr=ids.dest_ptr) %} // <--- FAILS HERE
assert src_ptr = dest_ptr;
return _verify_continuity(infos=&infos[1], n_segments_minus_one=n_segments_minus_one - 1);
} This fails because
The current code works so long as this hint is called, but that's not possible in Cairo0 code -- which the OS is written in, hence the reason it doesn't work for SNOS. This PR fixes this by ensuring that the required normal segment is allocated up front instead of fixed later on, which I think works for all cases. Let me know if this is any clearer than it was, I know the problem is pretty detailed. I'd also be happy to jump on a call and walk through it with you. Thanks again! |
To make sure that this works for all cases, I'm thinking of adding at least two tests:
|
Also, there's an error from clippy |
@notlesh any news on this? |
Hey @gabrielbosio, I am taking on the remaining parts of this task, specifically ensuring our fix is tested appropriately. Initial takeaways
Our Cairo0 approach:
Let me know your thoughts. |
vm/src/tests/cairo_run_test.rs
Outdated
@@ -1,4 +1,13 @@ | |||
use crate::{tests::*, types::layout_name::LayoutName}; | |||
use std::rc::Rc; |
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.
Hi there, I know this is still in process but this line might lead to a criptic error when running wasm tests. To save you the headache Instead of using std
, try with crate::stdlib::rc::Rc
.
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.
Resolve in e4ef596
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.
Hi @whichqua I think the proposal makes sense, broadly speaking. I left some suggestions and comments regarding specific details.
@@ -0,0 +1,170 @@ | |||
from starkware.cairo.common.alloc import alloc |
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.
If this test file is based on this one I would add a comment with a link to that 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.
Resolved in 3c2c6bc
|
||
func main{}() { | ||
let (res, segments) = test_segment_arena(); | ||
// assert res[0] = 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.
Why is this commented?
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 need to do the actual assertions here.
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.
Resolved in 3c2c6bc
let (res, segments) = test_segment_arena(); | ||
// assert res[0] = 1; | ||
ret; | ||
} |
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.
Please add a final newline
vm/src/tests/cairo_run_test.rs
Outdated
proof_mode: false, | ||
..Default::default() | ||
}; | ||
cairo_run(program_data, &cairo_run_config, &mut hint_executor).expect("Execution 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.
Given that the Python test used as reference runs some assertions after running the program, I think it would be nice if this test includes those assertions too.
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.
Sure, I will have that.
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.
Resolved in 3c2c6bc
vm/src/tests/cairo_run_test.rs
Outdated
use crate::any_box; | ||
use crate::hint_processor::builtin_hint_processor::hint_utils::insert_value_into_ap; | ||
use crate::hint_processor::hint_processor_definition::HintReference; | ||
use crate::serde::deserialize_program::ApTracking; | ||
use crate::types::exec_scope::ExecutionScopes; | ||
use crate::vm::errors::hint_errors::HintError; | ||
use crate::vm::vm_core::VirtualMachine; | ||
use crate::Felt252; | ||
use indoc::indoc; | ||
use std::collections::HashMap; |
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.
Please move these imports outside the test function
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.
Resolved in 3c2c6bc
vm/src/tests/cairo_run_test.rs
Outdated
const SEGMENTS_ADD: &str = "memory[ap] = to_felt_or_relocatable(segments.add())"; | ||
|
||
fn segments_add( | ||
vm: &mut VirtualMachine, | ||
_exec_scopes: &mut ExecutionScopes, | ||
_ids_data: &HashMap<String, HintReference>, | ||
_ap_tracking: &ApTracking, | ||
_constants: &HashMap<String, Felt252>, | ||
) -> Result<(), HintError> { | ||
let segment = vm.add_memory_segment(); | ||
insert_value_into_ap(vm, segment) | ||
} | ||
|
||
const SETUP_SEGMENT_INDEX: &str = indoc! {r#"if 'segment_index_to_arena_index' not in globals(): | ||
# A map from the relocatable value segment index to the index in the arena. | ||
segment_index_to_arena_index = {} | ||
|
||
# The segment is placed at the end of the arena. | ||
index = ids.n_segments | ||
|
||
# Create a segment or a temporary segment. | ||
start = segments.add_temp_segment() if index > 0 else segments.add() | ||
|
||
# Update 'SegmentInfo::start' and 'segment_index_to_arena_index'. | ||
ids.prev_segment_arena.infos[index].start = start | ||
segment_index_to_arena_index[start.segment_index] = index"#}; | ||
|
||
fn get_variable_from_root_exec_scope<T>( | ||
exec_scopes: &ExecutionScopes, | ||
name: &str, | ||
) -> Result<T, HintError> | ||
where | ||
T: Clone + 'static, | ||
{ | ||
exec_scopes.data[0] | ||
.get(name) | ||
.and_then(|var| var.downcast_ref::<T>().cloned()) | ||
.ok_or(HintError::VariableNotInScopeError( | ||
name.to_string().into_boxed_str(), | ||
)) | ||
} | ||
fn setup_segment_index( | ||
vm: &mut VirtualMachine, | ||
exec_scopes: &mut ExecutionScopes, | ||
ids_data: &HashMap<String, HintReference>, | ||
ap_tracking: &ApTracking, | ||
_constants: &HashMap<String, Felt252>, | ||
) -> Result<(), HintError> { | ||
let mut segment_index_to_arena_index: HashMap<isize, Felt252> = | ||
get_variable_from_root_exec_scope(exec_scopes, "segment_index_to_arena_index") | ||
.unwrap_or_default(); | ||
let n_segments = get_integer_from_var_name("n_segments", vm, ids_data, ap_tracking)?; | ||
let start = if n_segments > Felt252::ZERO { | ||
vm.add_temporary_segment() | ||
} else { | ||
vm.add_memory_segment() | ||
}; | ||
|
||
let prev_segment_arena_ptr = | ||
get_ptr_from_var_name("prev_segment_arena", vm, ids_data, ap_tracking)?; | ||
|
||
let infos_ptr = vm.get_relocatable(prev_segment_arena_ptr).unwrap(); | ||
|
||
let infos_start_ptr: crate::types::relocatable::Relocatable = | ||
vm.load_data(infos_ptr, &[start.into()]).unwrap(); | ||
|
||
vm.insert_value(infos_start_ptr, start)?; | ||
|
||
exec_scopes.insert_value("index", n_segments); | ||
exec_scopes.insert_value("start", start); | ||
|
||
segment_index_to_arena_index.insert(start.segment_index, n_segments); | ||
|
||
exec_scopes.data[0].insert( | ||
"segment_index_to_arena_index".to_string(), | ||
any_box!(segment_index_to_arena_index), | ||
); | ||
|
||
Ok(()) | ||
} | ||
|
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 suggest to move these hints to a helper file in the same directory than this 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.
I ended up moving the test into its own file as its pretty different with everything else in that file.
See 3c2c6bc
@gabrielbosio This is ready for review. |
@gabrielbosio @FrancoGiachetta Would be nice if we had the workflows running. |
@gabrielbosio I need your thoughts how to approach this failure. The hints are custom and are not implemented on the vm. |
There's a way to add custom hints and add them during the vm initialization (which might suit your case since it's a test). Check this link. It's an example of how to use it, it is about adding a hint which just prints a value. |
@FrancoGiachetta That is not the problem here. Our test contains a hint that is not in the vm |
Yes, I understand that. Custom hints are an easy way of defining hints that are no implemented in the vm. I think that should be the solution to the issue here. |
@FrancoGiachetta The hints themselves are already implemented here |
Mmm, I see. Perhaps it's not well formated? |
I dont think so, I can successfully run |
Okay. The issue seems to be that since your program is in cairo_programs/ it's being taken into account by that workflow. Since it uses the cairo-vm-cli to run it, that custom hints are not defined. I can only suggest you to move that code to a different location to see if that solves the problem. Create a new folder inside cairo-programs/ an place you program there. |
@gabrielbosio This should be good right? |
Co-authored-by: Stephen Shelton <[email protected]> Co-authored-by: Geoffrey Mureithi <[email protected]>
Also, all the commits in this PR must be signed before merging. |
I think I can add unit tests on the called methods. Would that be enough? |
I think it would 👍 |
Co-authored-by: notlesh <[email protected]>
9557d56
to
b8f841e
Compare
@gabrielbosio I had to work though the resigning of some past commits but ended up settling for a force push. Do you need @notlesh to also sign in this case? |
Any updates on this @gabrielbosio? |
@gabrielbosio Seems everything is ✅ |
(Note that this is basically a clone of Moonsong-Labs#45. We'd love to get this accepted upstream if possible.)
Problem: Starknet OS expects SegmentArena segments to all be temporary except the first segment, which must be normal. This is so that relocation rules for all segments can chain back into the first.
Solution: Use temporary segments only if it's not the first.
Description
The Starknet OS verifies the continuity of the segments in
SegmentArena
s here, which requires relocation rules to be properly set up.The requirements for this to work are that each
src_ptr
is a temporary segment and eachdest_ptr
is a non-temporary segment (or a temporary segment with an existing rule mapping it to a non-temporary segment).cairo-vm
uses a number of hints to accomplish this, and these are injected by the sierra-to-casm compiler. However, it relies on one additional hint which is not injected anywhere in the case of SNOS.This hint seems to ensure the same condition needed in SNOS: that the initial segment is a normal segment and that all other segments are temporary segments with relocation rules pointing back to the initial one. However, it creates the normal segment inside this last hint rather than when the SegmentArena is initialized.
SNOS handles this by simply allocating the initial segment as a normal one as explained here. This seems to alleviate the need to later recreate the segment in
relocate_all_segments()
, allowing the implementation to work in SNOS without the hint mentioned above.Checklist