scroll-tech / scroll-tech/ceno

Decode statically in the emulator, sync prover and emulator

Open
#524 10 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
153
Forks
43
Avg merge
3d 13h
Merged PRs (30d)
4

Description

Summary

I am proposing here is that we move from away from a von Neumann architecture and towards a modified Harvard architecture.

Details

At the moment we mix up decoding and execution of the guest program. (That allows us to support self-modifying code: you can eg implement a JIT that writes some new machine code to a memory address, and then jumps there to execute that. Not that we ever want to do that.)

I am suggesting that we stop supporting self-modifying code, and that we decode ahead of time.

Specifically our Program type would change as follows:

/// A RISC Zero program
 #[derive(Clone, Debug)]
 pub struct Program {
     /// The entrypoint of the program
     pub entry: u32,
     /// This is the lowest address of the program's executable code
     pub base_address: u32,
     /// The instructions of the program
-    pub instructions: Vec<u32>,
+    pub instructions: BTreeMap<WordAddr, DecodedInstruction>,
     /// The initial memory image
     pub image: BTreeMap<u32, u32>,
 }

And DecodedInstruction would become a much simpler structure with entries that are directly useful both for the emulator and for the circuits:

#[derive(Clone, Copy, Debug)]
pub struct DecodedInstruction {
    pub kind: InsnKind,
    /// The index of the first source register.
    ///
    /// Instructions that do not read a source register will have this set to 0.
    pub rs1: u32,
    /// The index of the second source register.
    ///
    /// Instructions that do not read a second source register will have this set to 0.
    pub rs2: u32,
    /// The index of the destination register.
    ///
    /// Instructions that do not write to a destination register will have this set to `RD_NULL`.
    pub rd: u32,
    /// The immediate value.
    ///
    /// This should be able to handle i32::MIN to u32::MAX, so we need i64.
    /// Conversion to a field element can happen with eg `crate::utils::i64_to_base`
    ///
    /// Instructions that don't use an immediate value will have this set to 0.
    pub imm: i64,

    /// The original encoded instruction word.
    ///
    /// This is useful for debugging and logging.
    /// TODO(Matthias): we still use this otherwise, remove those uses.
    #[allow(dead_code)]
    pub word: u32,
}

(Note: no weird fields with meanings that are too hard to explain.)

Stepping through the program in the emulator becomes simpler, but the real gain is that we no longer have to prove the relationship between the instruction as a u32-word and the decoded components of the instruction: the circuits no longer need to know that an instruction can even be encoded as a machine word. It's irrelevant to them.

See https://github.com/scroll-tech/ceno/pull/519 for a prototype

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the Program and DecodedInstruction definitions and compare the prototype in pull request 519; trace how decoding is currently shared by the emulator and prover circuits. Done means the proposed static instruction map and simplified decoded representation are integrated while keeping emulator and circuit behavior synchronized.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers, cryptography
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.