Skip to content

Commit

Permalink
serde: implement serialize_pointers (#75)
Browse files Browse the repository at this point in the history
Co-authored-by: Clément Walter <clement0walter@gmail.com>
  • Loading branch information
tcoratger and ClementWalter authored Oct 23, 2024
1 parent 484f6b9 commit 2b58f55
Show file tree
Hide file tree
Showing 3 changed files with 2,586 additions and 13 deletions.
200 changes: 187 additions & 13 deletions crates/exex/src/serde.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use cairo_vm::{
serde::deserialize_program::Identifier,
vm::{runners::cairo_runner::CairoRunner, vm_memory::memory::Memory},
types::{
errors::math_errors::MathError,
relocatable::{MaybeRelocatable, Relocatable},
},
vm::{errors::memory_errors::MemoryError, runners::cairo_runner::CairoRunner},
Felt252,
};
use std::collections::HashMap;
use thiserror::Error;

/// Represents errors that can occur during the serialization and deserialization processes between
Expand All @@ -27,6 +33,14 @@ pub enum KakarotSerdeError {
/// The number of matching identifiers found.
count: usize,
},

/// Error variant indicating a Math error in CairoVM operations
#[error(transparent)]
CairoVmMath(#[from] MathError),

/// Error variant indicating a memory error in CairoVM operations
#[error(transparent)]
CairoVmMemory(#[from] MemoryError),
}

/// A structure representing the Kakarot serialization and deserialization context for Cairo
Expand All @@ -35,7 +49,6 @@ pub enum KakarotSerdeError {
/// This struct encapsulates the components required to serialize and deserialize
/// Kakarot programs, including:
/// - The Cairo runner responsible for executing the program
/// - The memory
#[allow(missing_debug_implementations)]
pub struct KakarotSerde {
/// The Cairo runner used to execute Kakarot programs.
Expand All @@ -45,12 +58,6 @@ pub struct KakarotSerde {
/// It is responsible for handling program execution flow, managing state, and
/// providing access to program identifiers.
runner: CairoRunner,

/// The memory used to store memory cells inside segments and relocation rules.
///
/// This is used to have direct access to the memory cells and their values.
#[allow(dead_code)]
memory: Memory,
}

impl KakarotSerde {
Expand Down Expand Up @@ -96,6 +103,49 @@ impl KakarotSerde {
}),
}
}

/// Serializes a pointer to a Hashmap by resolving its members from memory.
///
/// We provide:
/// - The name of the struct whose pointer is being serialized.
/// - The memory location (pointer) of the struct.
///
/// We expect:
/// - A map of member names to their corresponding values (or `None` if the pointer is 0).
pub fn serialize_pointers(
&self,
struct_name: &str,
ptr: Relocatable,
) -> Result<HashMap<String, Option<MaybeRelocatable>>, KakarotSerdeError> {
// Fetch the struct definition (identifier) by name.
let identifier = self.get_identifier(struct_name, Some("struct".to_string()))?;

// Initialize the output map.
let mut output = HashMap::new();

// If the struct has members, iterate over them to resolve their values from memory.
if let Some(members) = identifier.members {
for (name, member) in members {
// We try to resolve the member's value from memory.
if let Some(member_ptr) = self.runner.vm.get_maybe(&(ptr + member.offset)?) {
// Check for null pointer.
if member_ptr == MaybeRelocatable::Int(Felt252::ZERO) &&
member.cairo_type.ends_with('*')
{
// We insert `None` for cases such as `parent=cast(0, model.Parent*)`
//
// Null pointers are represented as `None`.
output.insert(name, None);
} else {
// Insert the resolved member pointer into the output map.
output.insert(name, Some(member_ptr));
}
}
}
}

Ok(output)
}
}

#[cfg(test)]
Expand All @@ -105,7 +155,7 @@ mod tests {

fn setup_kakarot_serde() -> KakarotSerde {
// Load the valid program content from a JSON file
let program_content = include_bytes!("../../../cairo/programs/os.json");
let program_content = include_bytes!("../testdata/keccak_add_uint256.json");

// Create a Program instance from the loaded bytes, specifying "main" as the entry point
let program = Program::from_bytes(program_content, Some("main")).unwrap();
Expand All @@ -114,7 +164,7 @@ mod tests {
let runner = CairoRunner::new(&program, LayoutName::plain, false, false).unwrap();

// Return an instance of KakarotSerde
KakarotSerde { runner, memory: Memory::new() }
KakarotSerde { runner }
}

#[test]
Expand All @@ -126,7 +176,7 @@ mod tests {
assert_eq!(
kakarot_serde.get_identifier("main", Some("function".to_string())).unwrap(),
Identifier {
pc: Some(3478),
pc: Some(96),
type_: Some("function".to_string()),
value: None,
full_name: None,
Expand All @@ -142,7 +192,9 @@ mod tests {
pc: None,
type_: Some("reference".to_string()),
value: None,
full_name: Some("starkware.cairo.common.memcpy.memcpy.__temp0".to_string()),
full_name: Some(
"starkware.cairo.common.uint256.word_reverse_endian.__temp0".to_string()
),
members: None,
cairo_type: Some("felt".to_string())
}
Expand Down Expand Up @@ -218,9 +270,131 @@ mod tests {
{
assert_eq!(struct_name, "ImplicitArgs");
assert_eq!(expected_type, Some("struct".to_string()));
assert_eq!(count, 63);
assert_eq!(count, 6);
} else {
panic!("Expected KakarotSerdeError::MultipleIdentifiersFound");
}
}

#[test]
fn test_serialize_pointer_not_struct() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();

// Add a new memory segment to the virtual machine (VM).
let base = kakarot_serde.runner.vm.add_memory_segment();

// Attempt to serialize pointer with "main", expecting an IdentifierNotFound error.
let result = kakarot_serde.serialize_pointers("main", base);

// Assert that the result is an error with the expected struct name and type.
match result {
Err(KakarotSerdeError::IdentifierNotFound { struct_name, expected_type }) => {
assert_eq!(struct_name, "main".to_string());
assert_eq!(expected_type, Some("struct".to_string()));
}
_ => panic!("Expected KakarotSerdeError::IdentifierNotFound, but got: {:?}", result),
}
}

#[test]
fn test_serialize_pointer_empty() {
// Setup the KakarotSerde instance
let kakarot_serde = setup_kakarot_serde();

// Serialize the pointers of the "ImplicitArgs" struct but without any memory segment.
let result = kakarot_serde
.serialize_pointers("main.ImplicitArgs", Relocatable::default())
.expect("failed to serialize pointers");

// The result should be an empty HashMap since there is no memory segment.
assert!(result.is_empty(),);
}

#[test]
fn test_serialize_pointer_valid() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();

// Insert relocatable values in memory
let base = kakarot_serde
.runner
.vm
.gen_arg(&vec![
MaybeRelocatable::Int(Felt252::ZERO),
MaybeRelocatable::RelocatableValue(Relocatable { segment_index: 10, offset: 11 }),
MaybeRelocatable::RelocatableValue(Relocatable { segment_index: 10, offset: 11 }),
])
.unwrap()
.get_relocatable()
.unwrap();

// Serialize the pointers of the "ImplicitArgs" struct using the new memory segment.
let result = kakarot_serde
.serialize_pointers("main.ImplicitArgs", base)
.expect("failed to serialize pointers");

// Assert that the result matches the expected serialized struct members.
assert_eq!(
result,
HashMap::from_iter([
("output_ptr".to_string(), None),
(
"range_check_ptr".to_string(),
Some(MaybeRelocatable::RelocatableValue(Relocatable {
segment_index: 10,
offset: 11
}))
),
(
"bitwise_ptr".to_string(),
Some(MaybeRelocatable::RelocatableValue(Relocatable {
segment_index: 10,
offset: 11
}))
),
])
);
}

#[test]
fn test_serialize_null_no_pointer() {
// Setup the KakarotSerde instance
let mut kakarot_serde = setup_kakarot_serde();

// Insert relocatable values in memory
let base = kakarot_serde
.runner
.vm
.gen_arg(&vec![
MaybeRelocatable::RelocatableValue(Relocatable { segment_index: 10, offset: 11 }),
MaybeRelocatable::Int(Felt252::ZERO),
MaybeRelocatable::Int(Felt252::from(55)),
])
.unwrap()
.get_relocatable()
.unwrap();

// Serialize the pointers of the "ImplicitArgs" struct using the new memory segment.
let result = kakarot_serde
.serialize_pointers("main.ImplicitArgs", base)
.expect("failed to serialize pointers");

// Assert that the result matches the expected serialized struct members.
assert_eq!(
result,
HashMap::from_iter([
(
"output_ptr".to_string(),
Some(MaybeRelocatable::RelocatableValue(Relocatable {
segment_index: 10,
offset: 11
}))
),
// Not a pointer so that we shouldn't have a `None`
("range_check_ptr".to_string(), Some(MaybeRelocatable::Int(Felt252::ZERO))),
("bitwise_ptr".to_string(), Some(MaybeRelocatable::Int(Felt252::from(55)))),
])
);
}
}
30 changes: 30 additions & 0 deletions crates/exex/testdata/keccak_add_uint256.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
%builtins output range_check bitwise

from starkware.cairo.common.keccak_utils.keccak_utils import keccak_add_uint256
from starkware.cairo.common.uint256 import Uint256
from starkware.cairo.common.cairo_builtins import BitwiseBuiltin
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.serialize import serialize_word

func main{output_ptr: felt*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*}() {
alloc_locals;

let (inputs) = alloc();
let inputs_start = inputs;

let num = Uint256(34623634663146736, 598249824422424658356);

keccak_add_uint256{inputs=inputs_start}(num=num, bigend=0);

assert inputs[0] = 34623634663146736;
assert inputs[1] = 0;
assert inputs[2] = 7954014063719006644;
assert inputs[3] = 32;

serialize_word(inputs[0]);
serialize_word(inputs[1]);
serialize_word(inputs[2]);
serialize_word(inputs[3]);

return ();
}
Loading

0 comments on commit 2b58f55

Please sign in to comment.