Skip to content

feat(breaking): add support for hint accessible scopes #2042

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

Merged

Conversation

enitrat
Copy link
Contributor

@enitrat enitrat commented Apr 1, 2025

Adds accessible scopes in hint and improves alias identifiers resolution

Description

This PR enhances the Cairo VM by adding support for accessible scopes in hints.

Key Changes:
Accessible Scopes in Hints:

  • Added accessible_scopes field to HintProcessorData and related structs.
  • Updated compile_hint to accept accessible_scopes parameter across multiple files (e.g., hint_processor_definition.rs, cairo_1_hint_processor.rs).
  • Initialized with default empty vector or specific scopes (e.g., main, main.get_number) where applicable.

Impact:

  • Enables any implementer of execute_hint to only use the locally available constants, avoiding eventual conflicts of names

Also, related to #2041: this solves my problem as by making a destination field we can resolve the alias.

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.

@enitrat enitrat force-pushed the feat/support-accessible-scopes-hints branch from 077b47d to a61564e Compare April 4, 2025 21:09
@enitrat enitrat requested a review from FrancoGiachetta April 4, 2025 21:09
@FrancoGiachetta
Copy link
Contributor

FrancoGiachetta commented Apr 7, 2025

Hi @enitrat! I don't think I get the benefit of this change. Could you expand on this? Or maybe practical example? What's the use case of this change, apart from specifying where the alias points to (if I understood correctly)?

@enitrat
Copy link
Contributor Author

enitrat commented Apr 7, 2025

@FrancoGiachetta the constants argument of execute_hint returns a mapping from scope -> value for each constand of the program.

The problem is that, when executing a hint, it is not known whether a constant is in scope or not. Let's say I have three files

a.cairo

const MY_CONST = 3;

b.cairo

const MY_CONST = 4;

c.cairo

from a import MY_CONST
func foo(){
%{
    ids.MY_CONST
%}
}

When executing the hint inside foo, ids.MY_CONST is supposed to be 3 because imported from a.cairo.

Now, the problem is that the current design of the hint executor does not enable differentiating between the constants that are effectively in scope of the hints (in our case, only ("a", "MY_CONST")) and those not in scope (here, ("b", "MY_CONST")).

Thus, it is impossible to work only with the constant name (MY_CONST in this case) - which I use to build an ids object that only contains constants accessible in the local scope.

What this PR does is:

  • Properly deserialize the destination of aliases (here, when I do from a import MY_CONST, and running this program, the variable MY_CONST is an alias that has for path ("__main__", "MY_CONST"), and for destination ("a", "MY_CONST")
  • Pass the accessible_scopes, taken from the compiled hint, to the execute_hint function, enabling resolving variables that are accessible in the current scope.

@enitrat
Copy link
Contributor Author

enitrat commented Apr 7, 2025

For example here

const UPPER_BOUND: &str = "starkware.cairo.common.math.assert_250_bit.UPPER_BOUND";
const SHIFT: &str = "starkware.cairo.common.math.assert_250_bit.SHIFT";
you have to specify the full path to the constant. Whereas if it were possible to resolve the aliases based on the accessible scopes, you'd be able to simply get "UPPER_BOUND" from the accessible constants.

@FrancoGiachetta
Copy link
Contributor

Oh, I see. What I don't get now is, what is the issue with the full path? What is it that are you unable to do with the full path?

@enitrat
Copy link
Contributor Author

enitrat commented Apr 7, 2025

If I have two variable named MY_CONST that exists in two different full files and I import one of them, there's a conflict because inside the hint I do not know which one is available in the current scope

@enitrat enitrat requested a review from FrancoGiachetta April 11, 2025 17:14
@JulianGCalderon
Copy link
Contributor

Hi @enitrat! Sorry for the delay. We've been discussing this with the team.

This PR addresses two issues:

  • accessible scopes not being available when executing/compiling a hint
  • destination not being available when deserializing a program

The latter is more straightforward, and thus could me moved to another PR. Could you split this in two different pull requests?

Accessible Scopes

I have the following setup (similar to yours):

a.cairo:

const CONST_VAR = 3;

b.cairo:

const CONST_VAR = 4;
const CONST_BIZ = 5;

c.cairo:

from a import CONST_VAR
from b import CONST_BIZ

func main() {
    alloc_locals;
    local foo: felt;

    %{
        ids.foo = ids.CONST_VAR
    %}

    assert foo = CONST_VAR;
    return ();
}

When I try to implement the hint in our VM, I have the following information:

exec_scopes = ExecutionScopes { ... }
hint_data = HintProcessorData {
    code: "ids.foo = ids.CONST_VAR",
    ap_tracking: ...
    ids_data: {
        "foo": HintReference { ... },
    }
}
constants = {
    "__main__.main.SIZEOF_LOCALS": 0x1,
    "a.CONST_VAR": 0x3,
    "b.CONST_VAR": 0x4,
    "b.CONST_BIZ": 0x5,
}

Even if I had accessible scopes for the current hint, I wouldn't be able to resolve to which constant does ids.CONST_VAR refer to. Shouldn't we need to add another mechanism on top of this to fully support it? Does it suffice in your case?

@enitrat
Copy link
Contributor Author

enitrat commented Apr 15, 2025

Hey @JulianGCalderon, here's how we can know what's in scope and what is not only based on the information made available in this PR:

    // Process constants and make them accessible in Python hints
    // Constants are in the form {"module.name": value} and are accessible if their module
    // is in the hint_accessible_scopes
    for (full_name, value) in constants {
        let parts: Vec<_> = full_name.split('.').collect();
        let const_name = parts.last().unwrap_or(&"").to_string();
        let module_path = parts[..parts.len() - 1].join(".");

        // Check if constant is directly accessible from current scope
        if hint_accessible_scopes.iter().any(|scope| module_path == *scope) {
            py_ids_dict
                .borrow_mut(py)
                .items
                .insert(const_name.to_string(), value.to_biguint().into_bound_py_any(py)?.into());
            continue;
        }

        // Check if constant is accessible through an alias in any accessible scope
        for scope in hint_accessible_scopes {
            let alias_path = format!("{}.{}", scope, const_name);

            if let Some(identifier) = identifiers.get(&alias_path) {
                if let Some(destination) = &identifier.destination {
                    if let Some(resolved_value) = constants.get(destination) {
                        py_ids_dict.borrow_mut(py).items.insert(
                            const_name.to_string(),
                            resolved_value.to_biguint().into_bound_py_any(py)?.into(),
                        );
                        break;
                    }
                }
            }
        }
    }

Shouldn't we need to add another mechanism on top of this to fully support it? Does it suffice in your case?

So to answer this, I think it's sufficient. I will proceed in splitting the PR in 2

@JulianGCalderon
Copy link
Contributor

@enitrat I get the idea of the code snippet. I have some questions:

  • What is py_ids_dict and py?
  • I see you are using identifiers but we don't currently have that in our hint processor implementation. Your custom hint processor contains this information, right?

Maybe I'll understand the snippet if I have more context, could you share the data that is stored in your hint processor?

@enitrat
Copy link
Contributor Author

enitrat commented Apr 15, 2025

What is py_ids_dict and py?

  • py is the Python-GIL obtained through PyO3
  • py_ids_dict is a Python Object created to be the equivalent of the ids object in the Python Cairo VM. It holds all the accessible identifiers in the program, and is made to be 100% compatible with the API of VmConsts in the Python VM.

I see you are using identifiers but we don't currently have that in our hint processor implementation. Your custom hint processor contains this information, right?

Indeed, we make the program identifiers accessible in a hint by dumping them in the execution_scopes object.

@JulianGCalderon
Copy link
Contributor

I'm trying to think of alternate ways to support this. This is a big change on the API and we would like to exhaust all other possibilities before modifying the hint processor.

Have you tried storing the hint accessible scopes inside of the hint processor? As an example, for the Cairo 1 hint processor, we store all the hint data inside of the hint processor instead of receiving it in compile_hint:

/// HintProcessor for Cairo 1 compiler hints.
pub struct Cairo1HintProcessor {
hints: HashMap<usize, Vec<Hint>>,
run_resources: RunResources,

@enitrat
Copy link
Contributor Author

enitrat commented Apr 15, 2025

Have you tried storing the hint accessible scopes inside of the hint processor? As an example, for the Cairo 1 hint processor, we store all the hint data inside of the hint processor instead of receiving it in compile_hint:

Not sure how i'd do that, every hint has its own accessible scopes, they're not shared among all hints 🤔

Have you tried storing the hint accessible scopes inside of the hint processor? As an example, for the Cairo 1 hint processor, we store all the hint data inside of the hint processor instead of receiving it in compile_hint:

I think this is what i'm doing by storing them in the HintProcessorData ?

@JulianGCalderon
Copy link
Contributor

JulianGCalderon commented Apr 15, 2025

The Cairo 1 hint representation is much different from the Cairo 0 hint representation. For this reason, instead of relying on the information passed to the compile_hint function, we actually store all the hints inside of the HintProcessor beforehand.

Something like this:

struct HintProcessorImplementation {
    /// stores a vector of hints for each instruction index
    hints: HashMap<usize, Vec<Hint>>,
    ...
}

Relevant links:

This is kind of a workaround to the actual problem (not knowing accessible scopes), but it may work. Let me know what you think.

@enitrat
Copy link
Contributor Author

enitrat commented Apr 15, 2025

This is kind of a workaround to the actual problem (not knowing accessible scopes)

Yeah, i'm not a huge fan, I think that the best place for this information to live is currently inside the HintProcessorData. It does create some API changes so I understand the reluctance but I think it's the most logical place for it to be. I also don't really see any obvious workaround that would have cause minimum changes on that topic

@JulianGCalderon
Copy link
Contributor

Hey @enitrat, we would like to move forward with this PR.

  1. Could you split this PR in two? Please create another PR for adding identifier.destination, so that we can keep the discussion history on this one.
  2. Could you update the changelog for this PR? Make sure to label it as "BREAKING"

Thanks!

@enitrat
Copy link
Contributor Author

enitrat commented Apr 16, 2025

Hi @JulianGCalderon here's the PR for identifiers destinations:

#2071

@enitrat enitrat force-pushed the feat/support-accessible-scopes-hints branch from b70b814 to 42820a5 Compare April 16, 2025 17:13
@enitrat enitrat changed the title feat: add support for hint accessible scopes & aliases resolution feat(breaking): add support for hint accessible scopes Apr 16, 2025
Copy link
Contributor

@JulianGCalderon JulianGCalderon left a comment

Choose a reason for hiding this comment

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

Small typo

Copy link
Contributor

@JulianGCalderon JulianGCalderon left a comment

Choose a reason for hiding this comment

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

LGTM

@gabrielbosio gabrielbosio added this pull request to the merge queue Apr 17, 2025
Merged via the queue into lambdaclass:main with commit 3b99e12 Apr 17, 2025
88 of 91 checks passed
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.

4 participants