Skip to content

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

Conversation

notlesh
Copy link
Contributor

@notlesh notlesh commented Sep 25, 2024

(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 SegmentArenas 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 each dest_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

  • Linked to Github Issue
  • Unit tests added
  • Integration tests added.
  • This change requires new documentation.
    • Documentation has been added/updated.
    • CHANGELOG has been updated.

@pefontana
Copy link
Collaborator

pefontana commented Sep 27, 2024

Hi @notlesh
Thanks for the contribution!
There is just a minor error in the CI in the make check-fmt check
Also, would it be possible to add a small program or a little test for this fix?

@notlesh
Copy link
Contributor Author

notlesh commented Sep 29, 2024

Hi @notlesh Thanks for the contribution! There is just a minor error in the CI in the make check-fmt check Also, would it be possible to add a small program or a little test for this fix?

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 RelocateAllDictionaries cheatcode being called -- this is fundamentally what was broken and what is fixed here.

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.

@pefontana
Copy link
Collaborator

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 doesn't reproduce the exact error? Can we add some Cairo program that plays with dictionaries triggering the error? O maybe just add some unit tests

Also, there are merge conflicts that need to let me approve the CI run, can you solve them?

@notlesh
Copy link
Contributor Author

notlesh commented Jan 3, 2025

What do you mean by it doesn't reproduce the exact error? Can we add some Cairo program that plays with dictionaries triggering the error? O maybe just add some unit tests

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.

@notlesh notlesh requested a review from gabrielbosio as a code owner January 8, 2025 17:50
@gabrielbosio
Copy link
Collaborator

Let me know what you think and if you want more details or want to know how you can reproduce the exact error yourself.

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 new_default_dict already enforces the temporary segments to be all of the segments except the first one.
Once we have the code we can think of something smaller like a unit test.

@notlesh
Copy link
Contributor Author

notlesh commented Jan 8, 2025

Let me know what you think and if you want more details or want to know how you can reproduce the exact error yourself.

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 new_default_dict already enforces the temporary segments to be all of the segments except the first one. Once we have the code we can think of something smaller like a unit test.

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 memory.add_relocation_rule():

// 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 src_ptr must be a temporary segment. cairo-vm currently does this, basically:

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!

@gabrielbosio
Copy link
Collaborator

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.

To make sure that this works for all cases, I'm thinking of adding at least two tests:

  • Check that this works for Cairo 0. This could be achieved by calling the validate_segment_arena without failing.

  • Check that this works for Cairo 1. This could be achieved with a Cairo program that uses the alloc_felt_252_dict hint. For example, this program uses it:

    use dict::{felt252_dict_entry_finalize, Felt252DictTrait};
    
    fn main() -> (felt252, felt252) {
        let mut dict = felt252_dict_new::<felt252>();
        dict.insert(1, 64);
        let val = dict.get(1);
        dict.insert(1, 75);
        let val2 = dict.get(1);
        dict.squash();
        return (val, val2);
    }
    

    And then run this program and check the segments the hint creates are the same than
    main. I've run this program in both main and this branch and they produced the same segments. The segments created by the hint were the ones that had index >= 6 so I think you can compare those instead of the entire memory.

@gabrielbosio
Copy link
Collaborator

Also, there's an error from clippy

@gabrielbosio
Copy link
Collaborator

@notlesh any news on this?

@whichqua whichqua requested review from yuvalsw and noaov1 as code owners March 19, 2025 10:17
@whichqua
Copy link
Contributor

whichqua commented Mar 19, 2025

Hey @gabrielbosio, I am taking on the remaining parts of this task, specifically ensuring our fix is tested appropriately.

Initial takeaways

  1. For Cairo 1, there are already existing tests that use the alloc_felt_252_dict as seen here and here. The tests are still passing and there is not much we are gonna add on this.
  2. For Cairo 0, we are going to add a test and this is not straight forward but I do have an attempt.

Our Cairo0 approach:

  • We have added a Cairo0 test that creates some segments and runs some transformations on these segments as seen on this file which is based on this test on the cairo-lang repo.
  • This test has some hints that are not available on cairo-vm, and hence my (currently failing) test is a little more customized as seen here.
    I would like to hear your opinion on this, specifically if this approach is what your team expects.

Let me know your thoughts.

@@ -1,4 +1,13 @@
use crate::{tests::*, types::layout_name::LayoutName};
use std::rc::Rc;
Copy link
Contributor

@FrancoGiachetta FrancoGiachetta Mar 19, 2025

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolve in e4ef596

Copy link
Collaborator

@gabrielbosio gabrielbosio left a 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
Copy link
Collaborator

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.

Copy link
Contributor

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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this commented?

Copy link
Contributor

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.

Copy link
Contributor

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;
}
Copy link
Collaborator

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

proof_mode: false,
..Default::default()
};
cairo_run(program_data, &cairo_run_config, &mut hint_executor).expect("Execution failed");
Copy link
Collaborator

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.

Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in 3c2c6bc

Comment on lines 1367 to 1376
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;
Copy link
Collaborator

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in 3c2c6bc

Comment on lines 1383 to 1463
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(())
}

Copy link
Collaborator

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.

Copy link
Contributor

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

@whichqua
Copy link
Contributor

@gabrielbosio This is ready for review.

@whichqua
Copy link
Contributor

@gabrielbosio @FrancoGiachetta Would be nice if we had the workflows running.

@whichqua
Copy link
Contributor

@gabrielbosio I need your thoughts how to approach this failure. The hints are custom and are not implemented on the vm.

@FrancoGiachetta
Copy link
Contributor

FrancoGiachetta commented Mar 21, 2025

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.

@whichqua
Copy link
Contributor

@FrancoGiachetta That is not the problem here. Our test contains a hint that is not in the vm

@FrancoGiachetta
Copy link
Contributor

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.

@whichqua
Copy link
Contributor

@FrancoGiachetta The hints themselves are already implemented here

@FrancoGiachetta
Copy link
Contributor

Mmm, I see. Perhaps it's not well formated?

@whichqua
Copy link
Contributor

I dont think so, I can successfully run make test without any problem. The failures come from this workflow.

@FrancoGiachetta
Copy link
Contributor

FrancoGiachetta commented Mar 24, 2025

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.
Let me know if that helps.

@whichqua
Copy link
Contributor

@gabrielbosio This should be good right?

gabrielbosio added a commit that referenced this pull request Mar 26, 2025
Co-authored-by: Stephen Shelton <[email protected]>
Co-authored-by: Geoffrey Mureithi <[email protected]>
@gabrielbosio
Copy link
Collaborator

@whichqua looks good, I'm waiting for the CI in a clone of this PR (#2029) to make sure the jobs that fail here pass in the clone.

@gabrielbosio
Copy link
Collaborator

@whichqua I've run the CI and the codecov job fails. It seems that you need to cover the cases where relocate_all_dictionaries returns Err.
Here's the link to the codecov analysis.

@gabrielbosio
Copy link
Collaborator

Also, all the commits in this PR must be signed before merging.

@whichqua
Copy link
Contributor

It seems that you need to cover the cases where relocate_all_dictionaries returns Err.

I think I can add unit tests on the called methods. Would that be enough?

@gabrielbosio
Copy link
Collaborator

I think it would 👍

@whichqua whichqua force-pushed the notlesh/segment-arena-relocation-fix-upstream branch from 9557d56 to b8f841e Compare April 1, 2025 09:57
@whichqua
Copy link
Contributor

whichqua commented Apr 1, 2025

@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?

@whichqua
Copy link
Contributor

whichqua commented Apr 2, 2025

Any updates on this @gabrielbosio?

@gabrielbosio
Copy link
Collaborator

Do you need @notlesh to also sign in this case?

It seems it's enough with what you did. I'm running the CI jobs in #2048 to check if coverage is OK.

@whichqua
Copy link
Contributor

whichqua commented Apr 4, 2025

@gabrielbosio Seems everything is ✅

@FrancoGiachetta FrancoGiachetta added this pull request to the merge queue Apr 4, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Apr 4, 2025
@FrancoGiachetta FrancoGiachetta added this pull request to the merge queue Apr 4, 2025
Merged via the queue into lambdaclass:main with commit 0038711 Apr 4, 2025
172 of 184 checks passed
@whichqua whichqua deleted the notlesh/segment-arena-relocation-fix-upstream branch April 8, 2025 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants