Skip to content

Commit

Permalink
Output builtin features for bootloader support (lambdaclass#1580)
Browse files Browse the repository at this point in the history
* Output builtin features for bootloader support

This commit introduces the following features/changes:

* Paging: pages can now be added to the output builtin. These pages are
  reflected in the public memory of the VM when exporting the public
  input.
* The state of the output builtin can now be modified using
  the new `set_state` method.
* The output builtin can now handle attributes. These are used to
  generate the fact topologies of the bootloader.

* clippy + coverage fixes

* revert pub(crate) for base field

* remove from_segment

* changelog

* Fix: use dedicated struct to store the output builtin state

The `get_state` and `set_state` methods now rely on the new
`OutputBuiltinState` struct. Rolled back the introduction of the base
field in `OutputBuiltinAdditionalData`.

* fix coverage
  • Loading branch information
odesenfans authored Mar 1, 2024
1 parent 061ba87 commit 4c08af0
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

* feat: Add cairo1-run output pretty-printing for felts, arrays/spans and dicts [#1630](https://github.com/lambdaclass/cairo-vm/pull/1630)

* feat: output builtin features for bootloader support [#1580](https://github.com/lambdaclass/cairo-vm/pull/1580)

#### [1.0.0-rc1] - 2024-02-23

* Bump `starknet-types-core` dependency version to 0.0.9 [#1628](https://github.com/lambdaclass/cairo-vm/pull/1628)
Expand Down
2 changes: 2 additions & 0 deletions vm/src/vm/errors/runner_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ pub enum RunnerError {
Trace(#[from] TraceError),
#[error("EcOp builtin: Invalid Point")]
InvalidPoint,
#[error("Page ({0}) is not on the expected segment {1}")]
PageNotOnSegment(Relocatable, usize),
}

#[cfg(test)]
Expand Down
188 changes: 185 additions & 3 deletions vm/src/vm/runners/builtin_runner/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@ use crate::stdlib::{collections::HashMap, prelude::*};
use crate::types::relocatable::{MaybeRelocatable, Relocatable};
use crate::vm::errors::memory_errors::MemoryError;
use crate::vm::errors::runner_errors::RunnerError;
use crate::vm::runners::cairo_pie::{BuiltinAdditionalData, OutputBuiltinAdditionalData};
use crate::vm::runners::cairo_pie::{
Attributes, BuiltinAdditionalData, OutputBuiltinAdditionalData, Pages, PublicMemoryPage,
};
use crate::vm::vm_core::VirtualMachine;
use crate::vm::vm_memory::memory::Memory;
use crate::vm::vm_memory::memory_segments::MemorySegmentManager;

use super::OUTPUT_BUILTIN_NAME;

#[derive(Debug, Clone, PartialEq)]
pub struct OutputBuiltinState {
pub base: usize,
pub pages: Pages,
pub attributes: Attributes,
}

#[derive(Debug, Clone)]
pub struct OutputBuiltinRunner {
base: usize,
pub(crate) pages: Pages,
pub(crate) attributes: Attributes,
pub(crate) stop_ptr: Option<usize>,
pub(crate) included: bool,
}
Expand All @@ -20,11 +31,21 @@ impl OutputBuiltinRunner {
pub fn new(included: bool) -> OutputBuiltinRunner {
OutputBuiltinRunner {
base: 0,
pages: HashMap::default(),
attributes: HashMap::default(),
stop_ptr: None,
included,
}
}

pub fn new_state(&mut self, base: usize, included: bool) {
self.base = base;
self.pages = HashMap::default();
self.attributes = HashMap::default();
self.stop_ptr = None;
self.included = included;
}

pub fn initialize_segments(&mut self, segments: &mut MemorySegmentManager) {
self.base = segments.add().segment_index as usize // segments.add() always returns a positive index
}
Expand Down Expand Up @@ -110,14 +131,64 @@ impl OutputBuiltinRunner {

pub fn get_additional_data(&self) -> BuiltinAdditionalData {
BuiltinAdditionalData::Output(OutputBuiltinAdditionalData {
pages: HashMap::default(),
attributes: HashMap::default(),
pages: self.pages.clone(),
attributes: self.attributes.clone(),
})
}

pub(crate) fn set_stop_ptr_offset(&mut self, offset: usize) {
self.stop_ptr = Some(offset)
}

pub fn set_state(&mut self, new_state: OutputBuiltinState) {
self.base = new_state.base;
self.pages = new_state.pages;
self.attributes = new_state.attributes;
}

pub fn get_state(&mut self) -> OutputBuiltinState {
OutputBuiltinState {
base: self.base,
pages: self.pages.clone(),
attributes: self.attributes.clone(),
}
}

pub fn add_page(
&mut self,
page_id: usize,
page_start: Relocatable,
page_size: usize,
) -> Result<(), RunnerError> {
if page_start.segment_index as usize != self.base {
return Err(RunnerError::PageNotOnSegment(page_start, self.base));
}

self.pages.insert(
page_id,
PublicMemoryPage {
start: page_start.offset,
size: page_size,
},
);

Ok(())
}

pub fn get_public_memory(&self) -> Result<Vec<(usize, usize)>, RunnerError> {
let size = self
.stop_ptr
.ok_or(RunnerError::NoStopPointer(Box::new(OUTPUT_BUILTIN_NAME)))?;

let mut public_memory: Vec<(usize, usize)> = (0..size).map(|i| (i, 0)).collect();
for (page_id, page) in self.pages.iter() {
for index in 0..page.size {
public_memory[page.start + index].1 = *page_id;
}
}

Ok(public_memory)
}
}

impl Default for OutputBuiltinRunner {
Expand Down Expand Up @@ -463,4 +534,115 @@ mod tests {
let memory = memory![((0, 0), 0), ((0, 1), 1), ((0, 2), 2), ((0, 3), 3)];
assert!(builtin.air_private_input(&memory).is_empty());
}

#[test]
fn set_state() {
let mut builtin = OutputBuiltinRunner::new(true);
assert_eq!(builtin.base, 0);

let new_state = OutputBuiltinState {
base: 10,
pages: HashMap::from([(1, PublicMemoryPage { start: 0, size: 3 })]),
attributes: HashMap::from([("gps_fact_topology".to_string(), vec![0, 2, 0])]),
};
builtin.set_state(new_state.clone());

assert_eq!(builtin.base, new_state.base);
assert_eq!(builtin.pages, new_state.pages);
assert_eq!(builtin.attributes, new_state.attributes);

let state = builtin.get_state();
assert_eq!(state, new_state);
}

#[test]
fn new_state() {
let mut builtin = OutputBuiltinRunner {
base: 10,
pages: HashMap::from([(1, PublicMemoryPage { start: 0, size: 3 })]),
attributes: HashMap::from([("gps_fact_topology".to_string(), vec![0, 2, 0])]),
stop_ptr: Some(10),
included: true,
};

let new_base = 11;
let new_included = false;
builtin.new_state(new_base, new_included);

assert_eq!(builtin.base, new_base);
assert!(builtin.pages.is_empty());
assert!(builtin.attributes.is_empty());
assert_eq!(builtin.stop_ptr, None);
assert_eq!(builtin.included, new_included);
}

#[test]
fn add_page() {
let mut builtin = OutputBuiltinRunner::new(true);
assert_eq!(
builtin.add_page(
1,
Relocatable {
segment_index: builtin.base() as isize,
offset: 0
},
3
),
Ok(())
);

assert_eq!(
builtin.pages,
HashMap::from([(1, PublicMemoryPage { start: 0, size: 3 }),])
)
}

#[test]
fn add_page_wrong_segment() {
let mut builtin = OutputBuiltinRunner::new(true);
let page_start = Relocatable {
segment_index: 18,
offset: 0,
};

let result = builtin.add_page(1, page_start, 3);
assert!(
matches!(result, Err(RunnerError::PageNotOnSegment(relocatable, base)) if relocatable == page_start && base == builtin.base())
)
}

#[test]
fn get_public_memory() {
let mut builtin = OutputBuiltinRunner::new(true);

builtin
.add_page(
1,
Relocatable {
segment_index: builtin.base() as isize,
offset: 2,
},
2,
)
.unwrap();

builtin
.add_page(
2,
Relocatable {
segment_index: builtin.base() as isize,
offset: 4,
},
3,
)
.unwrap();

builtin.stop_ptr = Some(7);

let public_memory = builtin.get_public_memory().unwrap();
assert_eq!(
public_memory,
vec![(0, 0), (1, 0), (2, 1), (3, 1), (4, 2), (5, 2), (6, 2)]
);
}
}
6 changes: 3 additions & 3 deletions vm/src/vm/runners/cairo_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ use num_traits::{ToPrimitive, Zero};
use serde::{Deserialize, Serialize};

use super::{
builtin_runner::{KeccakBuiltinRunner, PoseidonBuiltinRunner, OUTPUT_BUILTIN_NAME},
builtin_runner::{KeccakBuiltinRunner, PoseidonBuiltinRunner},
cairo_pie::{self, CairoPie, CairoPieMetadata, CairoPieVersion},
};

Expand Down Expand Up @@ -1090,8 +1090,8 @@ impl CairoRunner {
let (_, size) = builtin_runner
.get_used_cells_and_allocated_size(vm)
.map_err(RunnerError::FinalizeSegements)?;
if builtin_runner.name() == OUTPUT_BUILTIN_NAME {
let public_memory = (0..size).map(|i| (i, 0)).collect();
if let BuiltinRunner::Output(output_builtin) = builtin_runner {
let public_memory = output_builtin.get_public_memory()?;
vm.segments
.finalize(Some(size), builtin_runner.base(), Some(&public_memory))
} else {
Expand Down

0 comments on commit 4c08af0

Please sign in to comment.