Skip to content
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

Implement Range for OpStack #338

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

cyberbono3
Copy link

Closes #322

@jan-ferdinand
Copy link
Member

Thanks for taking this on! Unfortunately, there is a logic error in the implementation. The documentation of field stack on OpStack reads:

/// The underlying, actual stack. When manually accessing, be aware of reversed indexing:
/// while `op_stack[0]` is the top of the stack, `op_stack.stack[0]` is the lowest element in
/// the stack.

Consequently, accessing op_stack[0..2] (equivalent to op_stack.index(0..2) or op_stack.index_mut(0..2) depending on context and determined by the compiler) should give op_stack.stack[op_stack.stack.len() - 2..op_stack.stack.len()].reverse() or similar. Note that this probably (a) doesn't compile and (b) is likely to contain off-by-one errors.

Copy link
Member

Choose a reason for hiding this comment

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

This file is only generated due to a bug in proptest. It has purposefully not been commited.

Copy link
Author

@cyberbono3 cyberbono3 Dec 12, 2024

Choose a reason for hiding this comment

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

I have added processor.txt into .gitignore. Does it sound good to you ?

@@ -4,6 +4,7 @@ use std::fmt::Result as FmtResult;
use std::num::TryFromIntError;
use std::ops::Index;
use std::ops::IndexMut;
use std::ops::{Range, RangeInclusive};
Copy link
Member

Choose a reason for hiding this comment

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

In this repository, we use an import granularity of item in order to trivialize git merging of imports.

Copy link
Author

Choose a reason for hiding this comment

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

I have merged all std::ops imports together: use std::ops::{Index, IndexMut, Range, RangeInclusive};

if range.end <= self.stack.len() && range.start < range.end {
&self.stack[range]
} else {
panic!("a range is out of bounds")
Copy link
Member

@jan-ferdinand jan-ferdinand Nov 7, 2024

Choose a reason for hiding this comment

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

I think's it's cleaner to just use the underlying panics, should there be any:

  • By not implementing these checks yourself, you cannot introduce logic bugs in them. Concretely, the range check in the impl Index<RangeInclusive<usize>> for OpStack looks suspiciously similar to the one in impl Index<Range<usize>> for OpStack.
  • The panic's message will correctly reflect the kind of access violation. For example, if the range is invalid, the current implementation will display an error regarding range bounds, whereas rust's panic message reads “slice index starts at x but ends at y.”
  • By dropping the manual bounds-check here people will see the accustomed message “index out of bounds: the len is x but the index is y.”
  • The stacktrace will point to the relevant method in either case.

@cyberbono3
Copy link
Author

@jan-ferdinand Sorry for delay. Thanks for your clarifications. Any thoughts on my update?

Comment on lines +185 to +191
fn compute_range(start: usize, end: usize, len: usize) -> (usize, usize) {
match (start, end) {
(0, end) if end == len => (0, len),
(0, end) => (len - end, len),
_ => (len - end, len - start),
}
}
Copy link
Member

Choose a reason for hiding this comment

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

The name does not really describe what the function does. A better name could be flip_range, indicating that the range is mirrored or flipped along the “length” axis. There might be better names yet.

Furthermore, I don't think the special cases are necessary since they all collapse into the general case.

fn flip_range(start: usize, end: usize, len: usize) -> (usize, usize) {
    (len - end, len - start)
}

@@ -2,8 +2,7 @@ use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::num::TryFromIntError;
use std::ops::Index;
use std::ops::IndexMut;
use std::ops::{Index, IndexMut, Range, RangeInclusive};
Copy link
Member

Choose a reason for hiding this comment

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

That's the wrong way around. 🙂 You've applied item granularity “module”, whereas granularity “item” results in one line per used type. See also here.

let (start, end) = compute_range(range.start, range.end, self.stack.len());
let mut stack = self.stack[start..end].to_vec();
stack.reverse();
Box::leak(Box::new(stack))
Copy link
Member

Choose a reason for hiding this comment

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

That's a creative way of solving this. 😃 However, I feel uncomfortable with consciously introducing memory leaks.

The approach you take here indicates to me that implementing Index<Range<_>> for OpStack requires refactoring OpStack such that the internal field stack is always in the correct order, for example using a VecDeque. It's probably a good idea to make the field non-public at the same time. Please let me know if this is too much scope creep. 😌

Copy link
Author

@cyberbono3 cyberbono3 Dec 18, 2024

Choose a reason for hiding this comment

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

Introduction of VecDeque sounds a good idea.

pub struct OpStack {
    pub stack: VecDeque<BFieldElement>,

    underflow_io_sequence: Vec<UnderflowIO>,
}

impl OpStack {
    pub fn new(program_digest: Digest) -> Self {
        let mut stack = VecDeque::with_capacity(OpStackElement::COUNT);
        stack.extend(program_digest.values());

        Self {
            stack,
            underflow_io_sequence: vec![],
        }
    }

In this case we do not reverse program_digest and set self.stack[0] is a top of the stack.

Additionally, we can update OpStack.push, OpStack.pop to add new item to the beginning of the stack and remove item from the beginning of the stack, respectively. Vec<BFieldElement> lacks this functionality.

  pub fn push(&mut self, element: BFieldElement) {
        self.stack.push_front(element);
        self.record_underflow_io(UnderflowIO::Write);
    }

    pub fn pop(&mut self) -> Result<BFieldElement> {
        self.record_underflow_io(UnderflowIO::Read);
        self.stack.pop_front().ok_or(OpStackError::TooShallow)
    }

    pub fn insert(&mut self, index: OpStackElement, element: BFieldElement) {
        let insertion_index = usize::from(index);
        self.stack.insert(insertion_index, element);
        self.record_underflow_io(UnderflowIO::Write);
    }

Index<Range<usize>> for OpStack can be implemented like that :

impl Index<Range<usize>> for OpStack {
    type Output = [BFieldElement];

    fn index(&self, range: Range<usize>) -> &Self::Output {
        &self.stack.as_slices().0[range.start..range.end]
    }
}

I wonder if this make sense.

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.

Implement Index<Range<…>> for OpStack
2 participants