Repository: RustPython/RustPython (language: rust)

Issue:
Switch to bincode 2 beta and make serde optional
I think this'll be a draft til bincode 2 releases (which should hopefully be soonish?) but this is exactly what I wanted from it :blush: related to #2362 


Discussion / hints:
Also related is bincode-org/bincode#511

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 57721dc7d1..f6c04b20e8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -126,15 +126,6 @@ version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
-[[package]]
-name = "bincode"
-version = "1.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
-dependencies = [
- "serde",
-]
-
 [[package]]
 name = "bit-set"
 version = "0.5.3"
@@ -1390,7 +1381,6 @@ dependencies = [
  "autocfg",
  "num-integer",
  "num-traits",
- "serde",
 ]
 
 [[package]]
@@ -1400,7 +1390,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
 dependencies = [
  "num-traits",
- "serde",
 ]
 
 [[package]]
@@ -1987,7 +1976,6 @@ dependencies = [
  "rustpython-ast",
  "rustpython-compiler-core",
  "rustpython-parser",
- "thiserror",
 ]
 
 [[package]]
@@ -2022,23 +2010,18 @@ dependencies = [
  "rustpython-codegen",
  "rustpython-compiler-core",
  "rustpython-parser",
- "thiserror",
 ]
 
 [[package]]
 name = "rustpython-compiler-core"
 version = "0.2.0"
 dependencies = [
- "bincode",
  "bitflags",
  "bstr",
  "itertools",
  "lz4_flex",
  "num-bigint",
  "num-complex",
- "num_enum",
- "serde",
- "thiserror",
 ]
 
 [[package]]
@@ -2108,7 +2091,6 @@ dependencies = [
  "rustc-hash",
  "rustpython-ast",
  "rustpython-compiler-core",
- "thiserror",
  "tiny-keccak",
  "unic-emoji-char",
  "unic-ucd-ident",
diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml
index b6e204dfdc..ff6ea77508 100644
--- a/compiler/Cargo.toml
+++ b/compiler/Cargo.toml
@@ -9,5 +9,3 @@ edition = "2021"
 rustpython-compiler-core = { path = "core" }
 rustpython-codegen = { path = "codegen" }
 rustpython-parser = { path = "parser" }
-
-thiserror = { workspace = true }
diff --git a/compiler/codegen/Cargo.toml b/compiler/codegen/Cargo.toml
index fd6ad6533f..3bb891ed3f 100644
--- a/compiler/codegen/Cargo.toml
+++ b/compiler/codegen/Cargo.toml
@@ -16,9 +16,8 @@ bitflags = { workspace = true }
 indexmap = { workspace = true }
 itertools = { workspace = true }
 log = { workspace = true }
-num-complex = { workspace = true, features = ["serde"] }
+num-complex = { workspace = true }
 num-traits = { workspace = true }
-thiserror = { workspace = true }
 
 [dev-dependencies]
 rustpython-parser = { path = "../parser" }
diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs
index be1c97db14..2e80de4ee2 100644
--- a/compiler/codegen/src/compile.rs
+++ b/compiler/codegen/src/compile.rs
@@ -249,9 +249,9 @@ impl Compiler {
     fn push_output(
         &mut self,
         flags: bytecode::CodeFlags,
-        posonlyarg_count: usize,
-        arg_count: usize,
-        kwonlyarg_count: usize,
+        posonlyarg_count: u32,
+        arg_count: u32,
+        kwonlyarg_count: u32,
         obj_name: String,
     ) {
         let source_path = self.source_path.clone();
@@ -936,9 +936,11 @@ impl Compiler {
 
         self.push_output(
             bytecode::CodeFlags::NEW_LOCALS | bytecode::CodeFlags::IS_OPTIMIZED,
-            args.posonlyargs.len(),
-            args.posonlyargs.len() + args.args.len(),
-            args.kwonlyargs.len(),
+            args.posonlyargs.len().try_into().unwrap(),
+            (args.posonlyargs.len() + args.args.len())
+                .try_into()
+                .unwrap(),
+            args.kwonlyargs.len().try_into().unwrap(),
             name.to_owned(),
         );
 
@@ -2750,8 +2752,8 @@ impl Compiler {
         self.current_source_location = location;
     }
 
-    fn get_source_line_number(&self) -> usize {
-        self.current_source_location.row()
+    fn get_source_line_number(&self) -> u32 {
+        self.current_source_location.row() as u32
     }
 
     fn push_qualified_path(&mut self, name: &str) {
diff --git a/compiler/codegen/src/error.rs b/compiler/codegen/src/error.rs
index 648446f4f2..e0380a62dd 100644
--- a/compiler/codegen/src/error.rs
+++ b/compiler/codegen/src/error.rs
@@ -2,7 +2,7 @@ use std::fmt;
 
 pub type CodegenError = rustpython_compiler_core::BaseError<CodegenErrorType>;
 
-#[derive(Debug, thiserror::Error)]
+#[derive(Debug)]
 #[non_exhaustive]
 pub enum CodegenErrorType {
     /// Invalid assignment, cannot store value in target.
@@ -33,6 +33,8 @@ pub enum CodegenErrorType {
     NotImplementedYet, // RustPython marker for unimplemented features
 }
 
+impl std::error::Error for CodegenErrorType {}
+
 impl fmt::Display for CodegenErrorType {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         use CodegenErrorType::*;
diff --git a/compiler/codegen/src/ir.rs b/compiler/codegen/src/ir.rs
index 43d7be0f2a..2658028d12 100644
--- a/compiler/codegen/src/ir.rs
+++ b/compiler/codegen/src/ir.rs
@@ -63,11 +63,11 @@ impl Default for Block {
 
 pub struct CodeInfo {
     pub flags: CodeFlags,
-    pub posonlyarg_count: usize, // Number of positional-only arguments
-    pub arg_count: usize,
-    pub kwonlyarg_count: usize,
+    pub posonlyarg_count: u32, // Number of positional-only arguments
+    pub arg_count: u32,
+    pub kwonlyarg_count: u32,
     pub source_path: String,
-    pub first_line_number: usize,
+    pub first_line_number: u32,
     pub obj_name: String, // Name of the object that created this code object
 
     pub blocks: Vec<Block>,
@@ -172,15 +172,15 @@ impl CodeInfo {
         }
     }
 
-    fn cell2arg(&self) -> Option<Box<[isize]>> {
+    fn cell2arg(&self) -> Option<Box<[i32]>> {
         if self.cellvar_cache.is_empty() {
             return None;
         }
 
         let total_args = self.arg_count
             + self.kwonlyarg_count
-            + self.flags.contains(CodeFlags::HAS_VARARGS) as usize
-            + self.flags.contains(CodeFlags::HAS_VARKEYWORDS) as usize;
+            + self.flags.contains(CodeFlags::HAS_VARARGS) as u32
+            + self.flags.contains(CodeFlags::HAS_VARKEYWORDS) as u32;
 
         let mut found_cellarg = false;
         let cell2arg = self
@@ -190,10 +190,10 @@ impl CodeInfo {
                 self.varname_cache
                     .get_index_of(var)
                     // check that it's actually an arg
-                    .filter(|i| *i < total_args)
+                    .filter(|i| *i < total_args as usize)
                     .map_or(-1, |i| {
                         found_cellarg = true;
-                        i as isize
+                        i as i32
                     })
             })
             .collect::<Box<[_]>>();
diff --git a/compiler/core/Cargo.toml b/compiler/core/Cargo.toml
index bf385a7182..03160356dc 100644
--- a/compiler/core/Cargo.toml
+++ b/compiler/core/Cargo.toml
@@ -8,14 +8,10 @@ repository = "https://github.com/RustPython/RustPython"
 license = "MIT"
 
 [dependencies]
-bincode = { workspace = true }
 bitflags = { workspace = true }
 bstr = { workspace = true }
 itertools = { workspace = true }
-num-bigint = { workspace = true, features = ["serde"] }
-num-complex = { workspace = true, features = ["serde"] }
-num_enum = { workspace = true }
-serde = { workspace = true, features = ["derive"] }
-thiserror = { workspace = true }
+num-bigint = { workspace = true }
+num-complex = { workspace = true }
 
 lz4_flex = "0.9.2"
diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs
index b7c456e989..3b3203186b 100644
--- a/compiler/core/src/bytecode.rs
+++ b/compiler/core/src/bytecode.rs
@@ -1,14 +1,12 @@
 //! Implement python as a virtual machine with bytecodes. This module
 //! implements bytecode structure.
 
-use crate::Location;
+use crate::marshal::MarshalError;
+use crate::{marshal, Location};
 use bitflags::bitflags;
-use bstr::ByteSlice;
 use itertools::Itertools;
 use num_bigint::BigInt;
 use num_complex::Complex64;
-use num_enum::{IntoPrimitive, TryFromPrimitive};
-use serde::{Deserialize, Serialize};
 use std::marker::PhantomData;
 use std::{collections::BTreeSet, fmt, hash, mem};
 
@@ -31,9 +29,7 @@ impl Constant for ConstantData {
             ConstantData::Str { value } => Str { value },
             ConstantData::Bytes { value } => Bytes { value },
             ConstantData::Code { code } => Code { code },
-            ConstantData::Tuple { elements } => Tuple {
-                elements: Box::new(elements.iter().map(|e| e.borrow_constant())),
-            },
+            ConstantData::Tuple { elements } => Tuple { elements },
             ConstantData::None => None,
             ConstantData::Ellipsis => Ellipsis,
         }
@@ -44,6 +40,9 @@ impl Constant for ConstantData {
 pub trait ConstantBag: Sized + Copy {
     type Constant: Constant;
     fn make_constant<C: Constant>(&self, constant: BorrowedConstant<C>) -> Self::Constant;
+    fn make_int(&self, value: BigInt) -> Self::Constant;
+    fn make_tuple(&self, elements: impl Iterator<Item = Self::Constant>) -> Self::Constant;
+    fn make_code(&self, code: CodeObject<Self::Constant>) -> Self::Constant;
     fn make_name(&self, name: &str) -> <Self::Constant as Constant>::Name;
 }
 
@@ -55,6 +54,19 @@ impl ConstantBag for BasicBag {
     fn make_constant<C: Constant>(&self, constant: BorrowedConstant<C>) -> Self::Constant {
         constant.to_owned()
     }
+    fn make_int(&self, value: BigInt) -> Self::Constant {
+        ConstantData::Integer { value }
+    }
+    fn make_tuple(&self, elements: impl Iterator<Item = Self::Constant>) -> Self::Constant {
+        ConstantData::Tuple {
+            elements: elements.collect(),
+        }
+    }
+    fn make_code(&self, code: CodeObject<Self::Constant>) -> Self::Constant {
+        ConstantData::Code {
+            code: Box::new(code),
+        }
+    }
     fn make_name(&self, name: &str) -> <Self::Constant as Constant>::Name {
         name.to_owned()
     }
@@ -62,26 +74,22 @@ impl ConstantBag for BasicBag {
 
 /// Primary container of a single code object. Each python function has
 /// a codeobject. Also a module has a codeobject.
-#[derive(Clone, Serialize, Deserialize)]
+#[derive(Clone)]
 pub struct CodeObject<C: Constant = ConstantData> {
     pub instructions: Box<[CodeUnit]>,
     pub locations: Box<[Location]>,
     pub flags: CodeFlags,
-    pub posonlyarg_count: usize,
+    pub posonlyarg_count: u32,
     // Number of positional-only arguments
-    pub arg_count: usize,
-    pub kwonlyarg_count: usize,
+    pub arg_count: u32,
+    pub kwonlyarg_count: u32,
     pub source_path: C::Name,
-    pub first_line_number: usize,
+    pub first_line_number: u32,
     pub max_stackdepth: u32,
     pub obj_name: C::Name,
     // Name of the object that created this code object
-    pub cell2arg: Option<Box<[isize]>>,
+    pub cell2arg: Option<Box<[i32]>>,
     pub constants: Box<[C]>,
-    #[serde(bound(
-        deserialize = "C::Name: serde::Deserialize<'de>",
-        serialize = "C::Name: serde::Serialize"
-    ))]
     pub names: Box<[C::Name]>,
     pub varnames: Box<[C::Name]>,
     pub cellvars: Box<[C::Name]>,
@@ -89,7 +97,6 @@ pub struct CodeObject<C: Constant = ConstantData> {
 }
 
 bitflags! {
-    #[derive(Serialize, Deserialize)]
     pub struct CodeFlags: u16 {
         const NEW_LOCALS = 0x01;
         const IS_GENERATOR = 0x02;
@@ -114,7 +121,7 @@ impl CodeFlags {
 }
 
 /// an opcode argument that may be extended by a prior ExtendedArg
-#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[derive(Copy, Clone, PartialEq, Eq)]
 #[repr(transparent)]
 pub struct OpArgByte(pub u8);
 impl OpArgByte {
@@ -211,23 +218,28 @@ impl OpArgType for bool {
     }
 }
 
-macro_rules! enum_oparg {
-    ($t:ident) => {
-        impl OpArgType for $t {
-            #[inline(always)]
-            fn from_oparg(x: u32) -> Option<Self> {
-                $t::try_from_primitive(x as _).ok()
-            }
-            #[inline(always)]
+macro_rules! oparg_enum {
+    ($(#[$attr:meta])* $vis:vis enum $name:ident { $($(#[$var_attr:meta])* $var:ident = $discr:literal,)* }) => {
+        $(#[$attr])*
+        $vis enum $name {
+            $($(#[$var_attr])* $var = $discr,)*
+        }
+
+        impl OpArgType for $name {
             fn to_oparg(self) -> u32 {
-                u8::from(self).into()
+                self as u32
+            }
+            fn from_oparg(x: u32) -> Option<Self> {
+                Some(match u8::try_from(x).ok()? {
+                    $($discr => Self::$var,)*
+                    _ => return None,
+                })
             }
         }
     };
 }
 
-#[derive(Copy, Clone, Serialize, Deserialize)]
-#[serde(bound = "")]
+#[derive(Copy, Clone)]
 pub struct Arg<T: OpArgType>(PhantomData<T>);
 
 impl<T: OpArgType> Arg<T> {
@@ -301,45 +313,45 @@ impl fmt::Display for Label {
     }
 }
 
-/// Transforms a value prior to formatting it.
-#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
-#[repr(u8)]
-pub enum ConversionFlag {
-    /// No conversion
-    None = 0, // CPython uses -1 but not pleasure for us
-    /// Converts by calling `str(<value>)`.
-    Str = b's',
-    /// Converts by calling `ascii(<value>)`.
-    Ascii = b'a',
-    /// Converts by calling `repr(<value>)`.
-    Repr = b'r',
-}
-enum_oparg!(ConversionFlag);
+oparg_enum!(
+    /// Transforms a value prior to formatting it.
+    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
+    #[repr(u8)]
+    pub enum ConversionFlag {
+        /// No conversion
+        None = 0, // CPython uses -1 but not pleasure for us
+        /// Converts by calling `str(<value>)`.
+        Str = b's',
+        /// Converts by calling `ascii(<value>)`.
+        Ascii = b'a',
+        /// Converts by calling `repr(<value>)`.
+        Repr = b'r',
+    }
+);
 
 impl TryFrom<usize> for ConversionFlag {
     type Error = usize;
     fn try_from(b: usize) -> Result<Self, Self::Error> {
-        u8::try_from(b)
-            .ok()
-            .and_then(|b| Self::try_from(b).ok())
-            .ok_or(b)
+        u32::try_from(b).ok().and_then(Self::from_oparg).ok_or(b)
     }
 }
 
-/// The kind of Raise that occurred.
-#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
-#[repr(u8)]
-pub enum RaiseKind {
-    Reraise,
-    Raise,
-    RaiseCause,
-}
-enum_oparg!(RaiseKind);
+oparg_enum!(
+    /// The kind of Raise that occurred.
+    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
+    #[repr(u8)]
+    pub enum RaiseKind {
+        Reraise = 0,
+        Raise = 1,
+        RaiseCause = 2,
+    }
+);
 
 pub type NameIdx = u32;
 
 /// A Single bytecode instruction.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[repr(u8)]
 pub enum Instruction {
     /// Importing by name
     ImportName {
@@ -562,7 +574,29 @@ pub enum Instruction {
 }
 const _: () = assert!(mem::size_of::<Instruction>() == 1);
 
-#[derive(Copy, Clone, Serialize, Deserialize)]
+impl From<Instruction> for u8 {
+    #[inline]
+    fn from(ins: Instruction) -> u8 {
+        // SAFETY: there's no padding bits
+        unsafe { std::mem::transmute::<Instruction, u8>(ins) }
+    }
+}
+
+impl TryFrom<u8> for Instruction {
+    type Error = crate::marshal::MarshalError;
+
+    #[inline]
+    fn try_from(value: u8) -> Result<Self, crate::marshal::MarshalError> {
+        if value <= u8::from(Instruction::ExtendedArg) {
+            Ok(unsafe { std::mem::transmute::<u8, Instruction>(value) })
+        } else {
+            Err(crate::marshal::MarshalError::InvalidBytecode)
+        }
+    }
+}
+
+#[derive(Copy, Clone)]
+#[repr(C)]
 pub struct CodeUnit {
     pub op: Instruction,
     pub arg: OpArgByte,
@@ -579,7 +613,6 @@ impl CodeUnit {
 use self::Instruction::*;
 
 bitflags! {
-    #[derive(Serialize, Deserialize)]
     pub struct MakeFunctionFlags: u8 {
         const CLOSURE = 0x01;
         const ANNOTATIONS = 0x02;
@@ -607,7 +640,7 @@ impl OpArgType for MakeFunctionFlags {
 /// let b = ConstantData::Boolean {value: false};
 /// assert_ne!(a, b);
 /// ```
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone)]
 pub enum ConstantData {
     Tuple { elements: Vec<ConstantData> },
     Integer { value: BigInt },
@@ -677,36 +710,40 @@ pub enum BorrowedConstant<'a, C: Constant> {
     Str { value: &'a str },
     Bytes { value: &'a [u8] },
     Code { code: &'a CodeObject<C> },
-    Tuple { elements: BorrowedTupleIter<'a, C> },
+    Tuple { elements: &'a [C] },
     None,
     Ellipsis,
 }
 
-type BorrowedTupleIter<'a, C> = Box<dyn Iterator<Item = BorrowedConstant<'a, C>> + 'a>;
+impl<C: Constant> Copy for BorrowedConstant<'_, C> {}
+impl<C: Constant> Clone for BorrowedConstant<'_, C> {
+    fn clone(&self) -> Self {
+        *self
+    }
+}
 
 impl<C: Constant> BorrowedConstant<'_, C> {
-    // takes `self` because we need to consume the iterator
-    pub fn fmt_display(self, f: &mut fmt::Formatter) -> fmt::Result {
+    pub fn fmt_display(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
             BorrowedConstant::Integer { value } => write!(f, "{value}"),
             BorrowedConstant::Float { value } => write!(f, "{value}"),
             BorrowedConstant::Complex { value } => write!(f, "{value}"),
             BorrowedConstant::Boolean { value } => {
-                write!(f, "{}", if value { "True" } else { "False" })
+                write!(f, "{}", if *value { "True" } else { "False" })
             }
             BorrowedConstant::Str { value } => write!(f, "{value:?}"),
-            BorrowedConstant::Bytes { value } => write!(f, "b{:?}", value.as_bstr()),
+            BorrowedConstant::Bytes { value } => write!(f, "b\"{}\"", value.escape_ascii()),
             BorrowedConstant::Code { code } => write!(f, "{code:?}"),
             BorrowedConstant::Tuple { elements } => {
                 write!(f, "(")?;
                 let mut first = true;
-                for c in elements {
+                for c in *elements {
                     if first {
                         first = false
                     } else {
                         write!(f, ", ")?;
                     }
-                    c.fmt_display(f)?;
+                    c.borrow_constant().fmt_display(f)?;
                 }
                 write!(f, ")")
             }
@@ -733,7 +770,10 @@ impl<C: Constant> BorrowedConstant<'_, C> {
                 code: Box::new(code.map_clone_bag(&BasicBag)),
             },
             BorrowedConstant::Tuple { elements } => Tuple {
-                elements: elements.map(BorrowedConstant::to_owned).collect(),
+                elements: elements
+                    .iter()
+                    .map(|c| c.borrow_constant().to_owned())
+                    .collect(),
             },
             BorrowedConstant::None => None,
             BorrowedConstant::Ellipsis => Ellipsis,
@@ -741,70 +781,74 @@ impl<C: Constant> BorrowedConstant<'_, C> {
     }
 }
 
-/// The possible comparison operators
-#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
-#[repr(u8)]
-pub enum ComparisonOperator {
-    // be intentional with bits so that we can do eval_ord with just a bitwise and
-    // bits: | Equal | Greater | Less |
-    Less = 0b001,
-    Greater = 0b010,
-    NotEqual = 0b011,
-    Equal = 0b100,
-    LessOrEqual = 0b101,
-    GreaterOrEqual = 0b110,
-}
-enum_oparg!(ComparisonOperator);
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
-#[repr(u8)]
-pub enum TestOperator {
-    In,
-    NotIn,
-    Is,
-    IsNot,
-    /// two exceptions that match?
-    ExceptionMatch,
-}
-enum_oparg!(TestOperator);
-
-/// The possible Binary operators
-/// # Examples
-///
-/// ```ignore
-/// use rustpython_compiler_core::Instruction::BinaryOperation;
-/// use rustpython_compiler_core::BinaryOperator::Add;
-/// let op = BinaryOperation {op: Add};
-/// ```
-#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
-#[repr(u8)]
-pub enum BinaryOperator {
-    Power,
-    Multiply,
-    MatrixMultiply,
-    Divide,
-    FloorDivide,
-    Modulo,
-    Add,
-    Subtract,
-    Lshift,
-    Rshift,
-    And,
-    Xor,
-    Or,
-}
-enum_oparg!(BinaryOperator);
-
-/// The possible unary operators
-#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
-#[repr(u8)]
-pub enum UnaryOperator {
-    Not,
-    Invert,
-    Minus,
-    Plus,
-}
-enum_oparg!(UnaryOperator);
+oparg_enum!(
+    /// The possible comparison operators
+    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
+    #[repr(u8)]
+    pub enum ComparisonOperator {
+        // be intentional with bits so that we can do eval_ord with just a bitwise and
+        // bits: | Equal | Greater | Less |
+        Less = 0b001,
+        Greater = 0b010,
+        NotEqual = 0b011,
+        Equal = 0b100,
+        LessOrEqual = 0b101,
+        GreaterOrEqual = 0b110,
+    }
+);
+
+oparg_enum!(
+    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
+    #[repr(u8)]
+    pub enum TestOperator {
+        In = 0,
+        NotIn = 1,
+        Is = 2,
+        IsNot = 3,
+        /// two exceptions that match?
+        ExceptionMatch = 4,
+    }
+);
+
+oparg_enum!(
+    /// The possible Binary operators
+    /// # Examples
+    ///
+    /// ```ignore
+    /// use rustpython_compiler_core::Instruction::BinaryOperation;
+    /// use rustpython_compiler_core::BinaryOperator::Add;
+    /// let op = BinaryOperation {op: Add};
+    /// ```
+    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
+    #[repr(u8)]
+    pub enum BinaryOperator {
+        Power = 0,
+        Multiply = 1,
+        MatrixMultiply = 2,
+        Divide = 3,
+        FloorDivide = 4,
+        Modulo = 5,
+        Add = 6,
+        Subtract = 7,
+        Lshift = 8,
+        Rshift = 9,
+        And = 10,
+        Xor = 11,
+        Or = 12,
+    }
+);
+
+oparg_enum!(
+    /// The possible unary operators
+    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
+    #[repr(u8)]
+    pub enum UnaryOperator {
+        Not = 0,
+        Invert = 1,
+        Minus = 2,
+        Plus = 3,
+    }
+);
 
 #[derive(Copy, Clone)]
 pub struct UnpackExArgs {
@@ -867,10 +911,10 @@ impl<C: Constant> CodeObject<C> {
     /// Get all arguments of the code object
     /// like inspect.getargs
     pub fn arg_names(&self) -> Arguments<C::Name> {
-        let nargs = self.arg_count;
-        let nkwargs = self.kwonlyarg_count;
+        let nargs = self.arg_count as usize;
+        let nkwargs = self.kwonlyarg_count as usize;
         let mut varargspos = nargs + nkwargs;
-        let posonlyargs = &self.varnames[..self.posonlyarg_count];
+        let posonlyargs = &self.varnames[..self.posonlyarg_count as usize];
         let args = &self.varnames[..nargs];
         let kwonlyargs = &self.varnames[nargs..varargspos];
 
@@ -1033,49 +1077,23 @@ impl<C: Constant> CodeObject<C> {
     }
 }
 
-/// Error that occurs during code deserialization
-#[derive(Debug)]
-#[non_exhaustive]
-pub enum CodeDeserializeError {
-    /// Unexpected End Of File
-    Eof,
-    /// Invalid Bytecode
-    Other,
-}
-
-impl fmt::Display for CodeDeserializeError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Self::Eof => f.write_str("unexpected end of data"),
-            Self::Other => f.write_str("invalid bytecode"),
-        }
-    }
-}
-
-impl std::error::Error for CodeDeserializeError {}
-
 impl CodeObject<ConstantData> {
     /// Load a code object from bytes
-    pub fn from_bytes(data: &[u8]) -> Result<Self, CodeDeserializeError> {
+    pub fn from_bytes(data: &[u8]) -> Result<Self, MarshalError> {
         use lz4_flex::block::DecompressError;
         let raw_bincode = lz4_flex::decompress_size_prepended(data).map_err(|e| match e {
             DecompressError::OutputTooSmall { .. } | DecompressError::ExpectedAnotherByte => {
-                CodeDeserializeError::Eof
+                MarshalError::Eof
             }
-            _ => CodeDeserializeError::Other,
+            _ => MarshalError::InvalidBytecode,
         })?;
-        let data = bincode::deserialize(&raw_bincode).map_err(|e| match *e {
-            bincode::ErrorKind::Io(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
-                CodeDeserializeError::Eof
-            }
-            _ => CodeDeserializeError::Other,
-        })?;
-        Ok(data)
+        marshal::deserialize_code(&mut &raw_bincode[..], BasicBag)
     }
 
     /// Serialize this bytecode to bytes.
     pub fn to_bytes(&self) -> Vec<u8> {
-        let data = bincode::serialize(&self).expect("CodeObject is not serializable");
+        let mut data = Vec::new();
+        marshal::serialize_code(&mut data, self);
         lz4_flex::compress_prepend_size(&data)
     }
 }
@@ -1466,7 +1484,7 @@ impl<C: Constant> fmt::Debug for CodeObject<C> {
 }
 
 /// A frozen module. Holds a code object and whether it is part of a package
-#[derive(Serialize, Deserialize, Debug)]
+#[derive(Debug)]
 pub struct FrozenModule {
     pub code: CodeObject<ConstantData>,
     pub package: bool,
@@ -1474,34 +1492,28 @@ pub struct FrozenModule {
 
 pub mod frozen_lib {
     use super::*;
-    use bincode::{options, Options};
-    use std::io;
+    use marshal::{Read, Write};
 
     /// Decode a library to a iterable of frozen modules
     pub fn decode_lib(bytes: &[u8]) -> FrozenModulesIter {
         let data = lz4_flex::decompress_size_prepended(bytes).unwrap();
-        let r = VecReader { data, pos: 0 };
-        let mut de = bincode::Deserializer::with_bincode_read(r, options());
-        let len = u64::deserialize(&mut de).unwrap().try_into().unwrap();
-        FrozenModulesIter { len, de }
+        let mut data = marshal::Cursor { data, position: 0 };
+        let remaining = data.read_u32().unwrap();
+        FrozenModulesIter { remaining, data }
     }
 
     pub struct FrozenModulesIter {
-        len: usize,
-        // ideally this could be a SeqAccess, but I think that would require existential types
-        de: bincode::Deserializer<VecReader, bincode::DefaultOptions>,
+        remaining: u32,
+        data: marshal::Cursor<Vec<u8>>,
     }
 
     impl Iterator for FrozenModulesIter {
         type Item = (String, FrozenModule);
 
         fn next(&mut self) -> Option<Self::Item> {
-            // manually mimic bincode's seq encoding, which is <len:u64> <element*len>
-            // This probably won't change (bincode doesn't require padding or anything), but
-            // it's not guaranteed by semver as far as I can tell
-            if self.len > 0 {
-                let entry = Deserialize::deserialize(&mut self.de).unwrap();
-                self.len -= 1;
+            if self.remaining > 0 {
+                let entry = read_entry(&mut self.data).unwrap();
+                self.remaining -= 1;
                 Some(entry)
             } else {
                 None
@@ -1509,12 +1521,19 @@ pub mod frozen_lib {
         }
 
         fn size_hint(&self) -> (usize, Option<usize>) {
-            (self.len, Some(self.len))
+            (self.remaining as usize, Some(self.remaining as usize))
         }
     }
-
     impl ExactSizeIterator for FrozenModulesIter {}
 
+    fn read_entry(rdr: &mut impl Read) -> Result<(String, FrozenModule), marshal::MarshalError> {
+        let len = rdr.read_u32()?;
+        let name = rdr.read_str(len)?.to_owned();
+        let code = marshal::deserialize_code(rdr, BasicBag)?;
+        let package = rdr.read_u8()? != 0;
+        Ok((name, FrozenModule { code, package }))
+    }
+
     /// Encode the given iterator of frozen modules into a compressed vector of bytes
     pub fn encode_lib<'a, I>(lib: I) -> Vec<u8>
     where
@@ -1522,82 +1541,25 @@ pub mod frozen_lib {
         I::IntoIter: ExactSizeIterator + Clone,
     {
         let iter = lib.into_iter();
-        let data = options().serialize(&SerializeLib { iter }).unwrap();
+        let mut data = Vec::new();
+        write_lib(&mut data, iter);
         lz4_flex::compress_prepend_size(&data)
     }
 
-    struct SerializeLib<I> {
-        iter: I,
-    }
-
-    impl<'a, I> Serialize for SerializeLib<I>
-    where
-        I: ExactSizeIterator<Item = (&'a str, &'a FrozenModule)> + Clone,
-    {
-        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-        where
-            S: serde::Serializer,
-        {
-            serializer.collect_seq(self.iter.clone())
+    fn write_lib<'a>(
+        buf: &mut impl Write,
+        lib: impl ExactSizeIterator<Item = (&'a str, &'a FrozenModule)>,
+    ) {
+        marshal::write_len(buf, lib.len());
+        for (name, module) in lib {
+            write_entry(buf, name, module);
         }
     }
 
-    /// Owned version of bincode::de::read::SliceReader<'a>
-    struct VecReader {
-        data: Vec<u8>,
-        pos: usize,
-    }
-
-    impl io::Read for VecReader {
-        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-            let mut subslice = &self.data[self.pos..];
-            let n = io::Read::read(&mut subslice, buf)?;
-            self.pos += n;
-            Ok(n)
-        }
-        fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
-            self.get_byte_slice(buf.len())
-                .map(|data| buf.copy_from_slice(data))
-        }
-    }
-
-    impl VecReader {
-        #[inline(always)]
-        fn get_byte_slice(&mut self, length: usize) -> io::Result<&[u8]> {
-            let subslice = &self.data[self.pos..];
-            match subslice.get(..length) {
-                Some(ret) => {
-                    self.pos += length;
-                    Ok(ret)
-                }
-                None => Err(io::ErrorKind::UnexpectedEof.into()),
-            }
-        }
-    }
-
-    impl<'storage> bincode::BincodeRead<'storage> for VecReader {
-        fn forward_read_str<V>(&mut self, length: usize, visitor: V) -> bincode::Result<V::Value>
-        where
-            V: serde::de::Visitor<'storage>,
-        {
-            let bytes = self.get_byte_slice(length)?;
-            match ::std::str::from_utf8(bytes) {
-                Ok(s) => visitor.visit_str(s),
-                Err(e) => Err(bincode::ErrorKind::InvalidUtf8Encoding(e).into()),
-            }
-        }
-
-        fn get_byte_buffer(&mut self, length: usize) -> bincode::Result<Vec<u8>> {
-            self.get_byte_slice(length)
-                .map(|x| x.to_vec())
-                .map_err(Into::into)
-        }
-
-        fn forward_read_bytes<V>(&mut self, length: usize, visitor: V) -> bincode::Result<V::Value>
-        where
-            V: serde::de::Visitor<'storage>,
-        {
-            visitor.visit_bytes(self.get_byte_slice(length)?)
-        }
+    fn write_entry(buf: &mut impl Write, name: &str, module: &FrozenModule) {
+        marshal::write_len(buf, name.len());
+        buf.write_slice(name.as_bytes());
+        marshal::serialize_code(buf, &module.code);
+        buf.write_u8(module.package as u8);
     }
 }
diff --git a/compiler/core/src/error.rs b/compiler/core/src/error.rs
index 9619e1d8ca..813ca9832f 100644
--- a/compiler/core/src/error.rs
+++ b/compiler/core/src/error.rs
@@ -1,4 +1,5 @@
 use crate::Location;
+use std::error::Error as StdError;
 use std::fmt::Display;
 
 #[derive(Debug, PartialEq, Eq)]
@@ -16,7 +17,14 @@ impl<T> std::ops::Deref for BaseError<T> {
     }
 }
 
-impl<T> std::error::Error for BaseError<T> where T: std::fmt::Display + std::fmt::Debug {}
+impl<T> StdError for BaseError<T>
+where
+    T: StdError + 'static,
+{
+    fn source(&self) -> Option<&(dyn StdError + 'static)> {
+        Some(&self.error)
+    }
+}
 
 impl<T> Display for BaseError<T>
 where
@@ -51,12 +59,18 @@ impl<T> BaseError<T> {
     }
 }
 
-#[derive(Debug, thiserror::Error)]
+#[derive(Debug)]
 pub struct CompileError<T> {
     pub body: BaseError<T>,
     pub statement: Option<String>,
 }
 
+impl<T: StdError + 'static> StdError for CompileError<T> {
+    fn source(&self) -> Option<&(dyn StdError + 'static)> {
+        self.body.source()
+    }
+}
+
 impl<T> std::ops::Deref for CompileError<T> {
     type Target = BaseError<T>;
     fn deref(&self) -> &Self::Target {
diff --git a/compiler/core/src/lib.rs b/compiler/core/src/lib.rs
index e1f0a5b35b..c431095f4a 100644
--- a/compiler/core/src/lib.rs
+++ b/compiler/core/src/lib.rs
@@ -4,6 +4,7 @@
 mod bytecode;
 mod error;
 mod location;
+pub mod marshal;
 mod mode;
 
 pub use bytecode::*;
diff --git a/compiler/core/src/location.rs b/compiler/core/src/location.rs
index d9cd59bea4..2a2d029d35 100644
--- a/compiler/core/src/location.rs
+++ b/compiler/core/src/location.rs
@@ -1,7 +1,5 @@
-use serde::{Deserialize, Serialize};
-
 /// Sourcecode location.
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub struct Location {
     pub(super) row: u32,
     pub(super) column: u32,
diff --git a/compiler/core/src/marshal.rs b/compiler/core/src/marshal.rs
new file mode 100644
index 0000000000..69d887934c
--- /dev/null
+++ b/compiler/core/src/marshal.rs
@@ -0,0 +1,615 @@
+use core::fmt;
+use std::convert::Infallible;
+
+use num_bigint::{BigInt, Sign};
+use num_complex::Complex64;
+
+use crate::{bytecode::*, Location};
+
+pub const FORMAT_VERSION: u32 = 4;
+
+#[derive(Debug)]
+pub enum MarshalError {
+    /// Unexpected End Of File
+    Eof,
+    /// Invalid Bytecode
+    InvalidBytecode,
+    /// Invalid utf8 in string
+    InvalidUtf8,
+    /// Bad type marker
+    BadType,
+}
+
+impl fmt::Display for MarshalError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::Eof => f.write_str("unexpected end of data"),
+            Self::InvalidBytecode => f.write_str("invalid bytecode"),
+            Self::InvalidUtf8 => f.write_str("invalid utf8"),
+            Self::BadType => f.write_str("bad type marker"),
+        }
+    }
+}
+
+impl From<std::str::Utf8Error> for MarshalError {
+    fn from(_: std::str::Utf8Error) -> Self {
+        Self::InvalidUtf8
+    }
+}
+
+impl std::error::Error for MarshalError {}
+
+type Result<T, E = MarshalError> = std::result::Result<T, E>;
+
+#[repr(u8)]
+enum Type {
+    // Null = b'0',
+    None = b'N',
+    False = b'F',
+    True = b'T',
+    StopIter = b'S',
+    Ellipsis = b'.',
+    Int = b'i',
+    Float = b'g',
+    Complex = b'y',
+    // Long = b'l',  // i32
+    Bytes = b's', // = TYPE_STRING
+    // Interned = b't',
+    // Ref = b'r',
+    Tuple = b'(',
+    List = b'[',
+    Dict = b'{',
+    Code = b'c',
+    Unicode = b'u',
+    // Unknown = b'?',
+    Set = b'<',
+    FrozenSet = b'>',
+    Ascii = b'a',
+    // AsciiInterned = b'A',
+    // SmallTuple = b')',
+    // ShortAscii = b'z',
+    // ShortAsciiInterned = b'Z',
+}
+// const FLAG_REF: u8 = b'\x80';
+
+impl TryFrom<u8> for Type {
+    type Error = MarshalError;
+    fn try_from(value: u8) -> Result<Self> {
+        use Type::*;
+        Ok(match value {
+            // b'0' => Null,
+            b'N' => None,
+            b'F' => False,
+            b'T' => True,
+            b'S' => StopIter,
+            b'.' => Ellipsis,
+            b'i' => Int,
+            b'g' => Float,
+            b'y' => Complex,
+            // b'l' => Long,
+            b's' => Bytes,
+            // b't' => Interned,
+            // b'r' => Ref,
+            b'(' => Tuple,
+            b'[' => List,
+            b'{' => Dict,
+            b'c' => Code,
+            b'u' => Unicode,
+            // b'?' => Unknown,
+            b'<' => Set,
+            b'>' => FrozenSet,
+            b'a' => Ascii,
+            // b'A' => AsciiInterned,
+            // b')' => SmallTuple,
+            // b'z' => ShortAscii,
+            // b'Z' => ShortAsciiInterned,
+            _ => return Err(MarshalError::BadType),
+        })
+    }
+}
+
+pub trait Read {
+    fn read_slice(&mut self, n: u32) -> Result<&[u8]>;
+    fn read_array<const N: usize>(&mut self) -> Result<&[u8; N]> {
+        self.read_slice(N as u32).map(|s| s.try_into().unwrap())
+    }
+    fn read_str(&mut self, len: u32) -> Result<&str> {
+        Ok(std::str::from_utf8(self.read_slice(len)?)?)
+    }
+    fn read_u8(&mut self) -> Result<u8> {
+        Ok(u8::from_le_bytes(*self.read_array()?))
+    }
+    fn read_u16(&mut self) -> Result<u16> {
+        Ok(u16::from_le_bytes(*self.read_array()?))
+    }
+    fn read_u32(&mut self) -> Result<u32> {
+        Ok(u32::from_le_bytes(*self.read_array()?))
+    }
+    fn read_u64(&mut self) -> Result<u64> {
+        Ok(u64::from_le_bytes(*self.read_array()?))
+    }
+}
+
+impl Read for &[u8] {
+    fn read_slice(&mut self, n: u32) -> Result<&[u8]> {
+        let data = self.get(..n as usize).ok_or(MarshalError::Eof)?;
+        *self = &self[n as usize..];
+        Ok(data)
+    }
+}
+
+pub struct Cursor<B> {
+    pub data: B,
+    pub position: usize,
+}
+
+impl<B: AsRef<[u8]>> Read for Cursor<B> {
+    fn read_slice(&mut self, n: u32) -> Result<&[u8]> {
+        let data = &self.data.as_ref()[self.position..];
+        let slice = data.get(..n as usize).ok_or(MarshalError::Eof)?;
+        self.position += n as usize;
+        Ok(slice)
+    }
+}
+
+pub fn deserialize_code<R: Read, Bag: ConstantBag>(
+    rdr: &mut R,
+    bag: Bag,
+) -> Result<CodeObject<Bag::Constant>> {
+    let len = rdr.read_u32()?;
+    let instructions = rdr.read_slice(len * 2)?;
+    let instructions = instructions
+        .chunks_exact(2)
+        .map(|cu| {
+            let op = Instruction::try_from(cu[0])?;
+            let arg = OpArgByte(cu[1]);
+            Ok(CodeUnit { op, arg })
+        })
+        .collect::<Result<Box<[CodeUnit]>>>()?;
+
+    let len = rdr.read_u32()?;
+    let locations = (0..len)
+        .map(|_| {
+            Ok(Location {
+                row: rdr.read_u32()?,
+                column: rdr.read_u32()?,
+            })
+        })
+        .collect::<Result<Box<[Location]>>>()?;
+
+    let flags = CodeFlags::from_bits_truncate(rdr.read_u16()?);
+
+    let posonlyarg_count = rdr.read_u32()?;
+    let arg_count = rdr.read_u32()?;
+    let kwonlyarg_count = rdr.read_u32()?;
+
+    let len = rdr.read_u32()?;
+    let source_path = bag.make_name(rdr.read_str(len)?);
+
+    let first_line_number = rdr.read_u32()?;
+    let max_stackdepth = rdr.read_u32()?;
+
+    let len = rdr.read_u32()?;
+    let obj_name = bag.make_name(rdr.read_str(len)?);
+
+    let len = rdr.read_u32()?;
+    let cell2arg = (len != 0)
+        .then(|| {
+            (0..len)
+                .map(|_| Ok(rdr.read_u32()? as i32))
+                .collect::<Result<Box<[i32]>>>()
+        })
+        .transpose()?;
+
+    let len = rdr.read_u32()?;
+    let constants = (0..len)
+        .map(|_| deserialize_value(rdr, bag))
+        .collect::<Result<Box<[_]>>>()?;
+
+    let mut read_names = || {
+        let len = rdr.read_u32()?;
+        (0..len)
+            .map(|_| {
+                let len = rdr.read_u32()?;
+                Ok(bag.make_name(rdr.read_str(len)?))
+            })
+            .collect::<Result<Box<[_]>>>()
+    };
+
+    let names = read_names()?;
+    let varnames = read_names()?;
+    let cellvars = read_names()?;
+    let freevars = read_names()?;
+
+    Ok(CodeObject {
+        instructions,
+        locations,
+        flags,
+        posonlyarg_count,
+        arg_count,
+        kwonlyarg_count,
+        source_path,
+        first_line_number,
+        max_stackdepth,
+        obj_name,
+        cell2arg,
+        constants,
+        names,
+        varnames,
+        cellvars,
+        freevars,
+    })
+}
+
+pub trait MarshalBag: Copy {
+    type Value;
+    fn make_bool(&self, value: bool) -> Self::Value;
+    fn make_none(&self) -> Self::Value;
+    fn make_ellipsis(&self) -> Self::Value;
+    fn make_float(&self, value: f64) -> Self::Value;
+    fn make_complex(&self, value: Complex64) -> Self::Value;
+    fn make_str(&self, value: &str) -> Self::Value;
+    fn make_bytes(&self, value: &[u8]) -> Self::Value;
+    fn make_int(&self, value: BigInt) -> Self::Value;
+    fn make_tuple(&self, elements: impl Iterator<Item = Self::Value>) -> Self::Value;
+    fn make_code(
+        &self,
+        code: CodeObject<<Self::ConstantBag as ConstantBag>::Constant>,
+    ) -> Self::Value;
+    fn make_stop_iter(&self) -> Result<Self::Value>;
+    fn make_list(&self, it: impl Iterator<Item = Self::Value>) -> Result<Self::Value>;
+    fn make_set(&self, it: impl Iterator<Item = Self::Value>) -> Result<Self::Value>;
+    fn make_frozenset(&self, it: impl Iterator<Item = Self::Value>) -> Result<Self::Value>;
+    fn make_dict(
+        &self,
+        it: impl Iterator<Item = (Self::Value, Self::Value)>,
+    ) -> Result<Self::Value>;
+    type ConstantBag: ConstantBag;
+    fn constant_bag(self) -> Self::ConstantBag;
+}
+
+impl<Bag: ConstantBag> MarshalBag for Bag {
+    type Value = Bag::Constant;
+    fn make_bool(&self, value: bool) -> Self::Value {
+        self.make_constant::<Bag::Constant>(BorrowedConstant::Boolean { value })
+    }
+    fn make_none(&self) -> Self::Value {
+        self.make_constant::<Bag::Constant>(BorrowedConstant::None)
+    }
+    fn make_ellipsis(&self) -> Self::Value {
+        self.make_constant::<Bag::Constant>(BorrowedConstant::Ellipsis)
+    }
+    fn make_float(&self, value: f64) -> Self::Value {
+        self.make_constant::<Bag::Constant>(BorrowedConstant::Float { value })
+    }
+    fn make_complex(&self, value: Complex64) -> Self::Value {
+        self.make_constant::<Bag::Constant>(BorrowedConstant::Complex { value })
+    }
+    fn make_str(&self, value: &str) -> Self::Value {
+        self.make_constant::<Bag::Constant>(BorrowedConstant::Str { value })
+    }
+    fn make_bytes(&self, value: &[u8]) -> Self::Value {
+        self.make_constant::<Bag::Constant>(BorrowedConstant::Bytes { value })
+    }
+    fn make_int(&self, value: BigInt) -> Self::Value {
+        self.make_int(value)
+    }
+    fn make_tuple(&self, elements: impl Iterator<Item = Self::Value>) -> Self::Value {
+        self.make_tuple(elements)
+    }
+    fn make_code(
+        &self,
+        code: CodeObject<<Self::ConstantBag as ConstantBag>::Constant>,
+    ) -> Self::Value {
+        self.make_code(code)
+    }
+    fn make_stop_iter(&self) -> Result<Self::Value> {
+        Err(MarshalError::BadType)
+    }
+    fn make_list(&self, _: impl Iterator<Item = Self::Value>) -> Result<Self::Value> {
+        Err(MarshalError::BadType)
+    }
+    fn make_set(&self, _: impl Iterator<Item = Self::Value>) -> Result<Self::Value> {
+        Err(MarshalError::BadType)
+    }
+    fn make_frozenset(&self, _: impl Iterator<Item = Self::Value>) -> Result<Self::Value> {
+        Err(MarshalError::BadType)
+    }
+    fn make_dict(
+        &self,
+        _: impl Iterator<Item = (Self::Value, Self::Value)>,
+    ) -> Result<Self::Value> {
+        Err(MarshalError::BadType)
+    }
+    type ConstantBag = Self;
+    fn constant_bag(self) -> Self::ConstantBag {
+        self
+    }
+}
+
+pub fn deserialize_value<R: Read, Bag: MarshalBag>(rdr: &mut R, bag: Bag) -> Result<Bag::Value> {
+    let typ = Type::try_from(rdr.read_u8()?)?;
+    let value = match typ {
+        Type::True => bag.make_bool(true),
+        Type::False => bag.make_bool(false),
+        Type::None => bag.make_none(),
+        Type::StopIter => bag.make_stop_iter()?,
+        Type::Ellipsis => bag.make_ellipsis(),
+        Type::Int => {
+            let len = rdr.read_u32()? as i32;
+            let sign = if len < 0 { Sign::Minus } else { Sign::Plus };
+            let bytes = rdr.read_slice(len.unsigned_abs())?;
+            let int = BigInt::from_bytes_le(sign, bytes);
+            bag.make_int(int)
+        }
+        Type::Float => {
+            let value = f64::from_bits(rdr.read_u64()?);
+            bag.make_float(value)
+        }
+        Type::Complex => {
+            let re = f64::from_bits(rdr.read_u64()?);
+            let im = f64::from_bits(rdr.read_u64()?);
+            let value = Complex64 { re, im };
+            bag.make_complex(value)
+        }
+        Type::Ascii | Type::Unicode => {
+            let len = rdr.read_u32()?;
+            let value = rdr.read_str(len)?;
+            bag.make_str(value)
+        }
+        Type::Tuple => {
+            let len = rdr.read_u32()?;
+            let it = (0..len).map(|_| deserialize_value(rdr, bag));
+            itertools::process_results(it, |it| bag.make_tuple(it))?
+        }
+        Type::List => {
+            let len = rdr.read_u32()?;
+            let it = (0..len).map(|_| deserialize_value(rdr, bag));
+            itertools::process_results(it, |it| bag.make_list(it))??
+        }
+        Type::Set => {
+            let len = rdr.read_u32()?;
+            let it = (0..len).map(|_| deserialize_value(rdr, bag));
+            itertools::process_results(it, |it| bag.make_set(it))??
+        }
+        Type::FrozenSet => {
+            let len = rdr.read_u32()?;
+            let it = (0..len).map(|_| deserialize_value(rdr, bag));
+            itertools::process_results(it, |it| bag.make_frozenset(it))??
+        }
+        Type::Dict => {
+            let len = rdr.read_u32()?;
+            let it = (0..len).map(|_| {
+                let k = deserialize_value(rdr, bag)?;
+                let v = deserialize_value(rdr, bag)?;
+                Ok::<_, MarshalError>((k, v))
+            });
+            itertools::process_results(it, |it| bag.make_dict(it))??
+        }
+        Type::Bytes => {
+            // Following CPython, after marshaling, byte arrays are converted into bytes.
+            let len = rdr.read_u32()?;
+            let value = rdr.read_slice(len)?;
+            bag.make_bytes(value)
+        }
+        Type::Code => bag.make_code(deserialize_code(rdr, bag.constant_bag())?),
+    };
+    Ok(value)
+}
+
+pub trait Dumpable: Sized {
+    type Error;
+    type Constant: Constant;
+    fn with_dump<R>(&self, f: impl FnOnce(DumpableValue<'_, Self>) -> R) -> Result<R, Self::Error>;
+}
+
+pub enum DumpableValue<'a, D: Dumpable> {
+    Integer(&'a BigInt),
+    Float(f64),
+    Complex(Complex64),
+    Boolean(bool),
+    Str(&'a str),
+    Bytes(&'a [u8]),
+    Code(&'a CodeObject<D::Constant>),
+    Tuple(&'a [D]),
+    None,
+    Ellipsis,
+    StopIter,
+    List(&'a [D]),
+    Set(&'a [D]),
+    Frozenset(&'a [D]),
+    Dict(&'a [(D, D)]),
+}
+
+impl<'a, C: Constant> From<BorrowedConstant<'a, C>> for DumpableValue<'a, C> {
+    fn from(c: BorrowedConstant<'a, C>) -> Self {
+        match c {
+            BorrowedConstant::Integer { value } => Self::Integer(value),
+            BorrowedConstant::Float { value } => Self::Float(value),
+            BorrowedConstant::Complex { value } => Self::Complex(value),
+            BorrowedConstant::Boolean { value } => Self::Boolean(value),
+            BorrowedConstant::Str { value } => Self::Str(value),
+            BorrowedConstant::Bytes { value } => Self::Bytes(value),
+            BorrowedConstant::Code { code } => Self::Code(code),
+            BorrowedConstant::Tuple { elements } => Self::Tuple(elements),
+            BorrowedConstant::None => Self::None,
+            BorrowedConstant::Ellipsis => Self::Ellipsis,
+        }
+    }
+}
+
+impl<C: Constant> Dumpable for C {
+    type Error = Infallible;
+    type Constant = Self;
+    #[inline(always)]
+    fn with_dump<R>(&self, f: impl FnOnce(DumpableValue<'_, Self>) -> R) -> Result<R, Self::Error> {
+        Ok(f(self.borrow_constant().into()))
+    }
+}
+
+pub trait Write {
+    fn write_slice(&mut self, slice: &[u8]);
+    fn write_u8(&mut self, v: u8) {
+        self.write_slice(&v.to_le_bytes())
+    }
+    fn write_u16(&mut self, v: u16) {
+        self.write_slice(&v.to_le_bytes())
+    }
+    fn write_u32(&mut self, v: u32) {
+        self.write_slice(&v.to_le_bytes())
+    }
+    fn write_u64(&mut self, v: u64) {
+        self.write_slice(&v.to_le_bytes())
+    }
+}
+
+impl Write for Vec<u8> {
+    fn write_slice(&mut self, slice: &[u8]) {
+        self.extend_from_slice(slice)
+    }
+}
+
+pub(crate) fn write_len<W: Write>(buf: &mut W, len: usize) {
+    let Ok(len) = len.try_into() else { panic!("too long to serialize") };
+    buf.write_u32(len);
+}
+
+pub fn serialize_value<W: Write, D: Dumpable>(
+    buf: &mut W,
+    constant: DumpableValue<'_, D>,
+) -> Result<(), D::Error> {
+    match constant {
+        DumpableValue::Integer(int) => {
+            buf.write_u8(Type::Int as u8);
+            let (sign, bytes) = int.to_bytes_le();
+            let len: i32 = bytes.len().try_into().expect("too long to serialize");
+            let len = if sign == Sign::Minus { -len } else { len };
+            buf.write_u32(len as u32);
+            buf.write_slice(&bytes);
+        }
+        DumpableValue::Float(f) => {
+            buf.write_u8(Type::Float as u8);
+            buf.write_u64(f.to_bits());
+        }
+        DumpableValue::Complex(c) => {
+            buf.write_u8(Type::Complex as u8);
+            buf.write_u64(c.re.to_bits());
+            buf.write_u64(c.im.to_bits());
+        }
+        DumpableValue::Boolean(b) => {
+            buf.write_u8(if b { Type::True } else { Type::False } as u8);
+        }
+        DumpableValue::Str(s) => {
+            buf.write_u8(Type::Unicode as u8);
+            write_len(buf, s.len());
+            buf.write_slice(s.as_bytes());
+        }
+        DumpableValue::Bytes(b) => {
+            buf.write_u8(Type::Bytes as u8);
+            write_len(buf, b.len());
+            buf.write_slice(b);
+        }
+        DumpableValue::Code(c) => {
+            buf.write_u8(Type::Code as u8);
+            serialize_code(buf, c);
+        }
+        DumpableValue::Tuple(tup) => {
+            buf.write_u8(Type::Tuple as u8);
+            write_len(buf, tup.len());
+            for val in tup {
+                val.with_dump(|val| serialize_value(buf, val))??
+            }
+        }
+        DumpableValue::None => {
+            buf.write_u8(Type::None as u8);
+        }
+        DumpableValue::Ellipsis => {
+            buf.write_u8(Type::Ellipsis as u8);
+        }
+        DumpableValue::StopIter => {
+            buf.write_u8(Type::StopIter as u8);
+        }
+        DumpableValue::List(l) => {
+            buf.write_u8(Type::List as u8);
+            write_len(buf, l.len());
+            for val in l {
+                val.with_dump(|val| serialize_value(buf, val))??
+            }
+        }
+        DumpableValue::Set(set) => {
+            buf.write_u8(Type::Set as u8);
+            write_len(buf, set.len());
+            for val in set {
+                val.with_dump(|val| serialize_value(buf, val))??
+            }
+        }
+        DumpableValue::Frozenset(set) => {
+            buf.write_u8(Type::FrozenSet as u8);
+            write_len(buf, set.len());
+            for val in set {
+                val.with_dump(|val| serialize_value(buf, val))??
+            }
+        }
+        DumpableValue::Dict(d) => {
+            buf.write_u8(Type::Dict as u8);
+            write_len(buf, d.len());
+            for (k, v) in d {
+                k.with_dump(|val| serialize_value(buf, val))??;
+                v.with_dump(|val| serialize_value(buf, val))??;
+            }
+        }
+    }
+    Ok(())
+}
+
+pub fn serialize_code<W: Write, C: Constant>(buf: &mut W, code: &CodeObject<C>) {
+    write_len(buf, code.instructions.len());
+    // SAFETY: it's ok to transmute CodeUnit to [u8; 2]
+    let (_, instructions_bytes, _) = unsafe { code.instructions.align_to() };
+    buf.write_slice(instructions_bytes);
+
+    write_len(buf, code.locations.len());
+    for loc in &*code.locations {
+        buf.write_u32(loc.row);
+        buf.write_u32(loc.column);
+    }
+
+    buf.write_u16(code.flags.bits());
+
+    buf.write_u32(code.posonlyarg_count);
+    buf.write_u32(code.arg_count);
+    buf.write_u32(code.kwonlyarg_count);
+
+    write_len(buf, code.source_path.as_ref().len());
+    buf.write_slice(code.source_path.as_ref().as_bytes());
+
+    buf.write_u32(code.first_line_number);
+    buf.write_u32(code.max_stackdepth);
+
+    write_len(buf, code.obj_name.as_ref().len());
+    buf.write_slice(code.obj_name.as_ref().as_bytes());
+
+    let cell2arg = code.cell2arg.as_deref().unwrap_or(&[]);
+    write_len(buf, cell2arg.len());
+    for &i in cell2arg {
+        buf.write_u32(i as u32)
+    }
+
+    write_len(buf, code.constants.len());
+    for constant in &*code.constants {
+        serialize_value(buf, constant.borrow_constant().into()).unwrap_or_else(|x| match x {})
+    }
+
+    let mut write_names = |names: &[C::Name]| {
+        write_len(buf, names.len());
+        for name in names {
+            write_len(buf, name.as_ref().len());
+            buf.write_slice(name.as_ref().as_bytes());
+        }
+    };
+
+    write_names(&code.names);
+    write_names(&code.varnames);
+    write_names(&code.cellvars);
+    write_names(&code.freevars);
+}
diff --git a/compiler/parser/Cargo.toml b/compiler/parser/Cargo.toml
index 40bf75e508..026616d394 100644
--- a/compiler/parser/Cargo.toml
+++ b/compiler/parser/Cargo.toml
@@ -26,7 +26,6 @@ itertools = { workspace = true }
 log = { workspace = true }
 num-bigint = { workspace = true }
 num-traits = { workspace = true }
-thiserror = { workspace = true }
 unicode_names2 = { workspace = true }
 
 unic-emoji-char = "0.9.0"
diff --git a/compiler/parser/src/parser.rs b/compiler/parser/src/parser.rs
index c2c4a38318..04875c44e3 100644
--- a/compiler/parser/src/parser.rs
+++ b/compiler/parser/src/parser.rs
@@ -200,7 +200,7 @@ pub fn parse_tokens(
 pub type ParseError = rustpython_compiler_core::BaseError<ParseErrorType>;
 
 /// Represents the different types of errors that can occur during parsing.
-#[derive(Debug, PartialEq, thiserror::Error)]
+#[derive(Debug, PartialEq)]
 pub enum ParseErrorType {
     /// Parser encountered an unexpected end of input
     Eof,
@@ -215,6 +215,8 @@ pub enum ParseErrorType {
     Lexical(LexicalErrorType),
 }
 
+impl std::error::Error for ParseErrorType {}
+
 // Convert `lalrpop_util::ParseError` to our internal type
 fn parse_error_from_lalrpop(
     err: LalrpopError<Location, Tok, LexicalError>,
diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs
index 804ce10ecb..27c158727f 100644
--- a/compiler/src/lib.rs
+++ b/compiler/src/lib.rs
@@ -7,12 +7,40 @@ use rustpython_parser::{
 pub use rustpython_codegen::compile::CompileOpts;
 pub use rustpython_compiler_core::{BaseError as CompileErrorBody, CodeObject, Mode};
 
-#[derive(Debug, thiserror::Error)]
+use std::error::Error as StdError;
+use std::fmt;
+
+#[derive(Debug)]
 pub enum CompileErrorType {
-    #[error(transparent)]
-    Codegen(#[from] rustpython_codegen::error::CodegenErrorType),
-    #[error(transparent)]
-    Parse(#[from] parser::ParseErrorType),
+    Codegen(rustpython_codegen::error::CodegenErrorType),
+    Parse(parser::ParseErrorType),
+}
+
+impl StdError for CompileErrorType {
+    fn source(&self) -> Option<&(dyn StdError + 'static)> {
+        match self {
+            CompileErrorType::Codegen(e) => e.source(),
+            CompileErrorType::Parse(e) => e.source(),
+        }
+    }
+}
+impl fmt::Display for CompileErrorType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            CompileErrorType::Codegen(e) => e.fmt(f),
+            CompileErrorType::Parse(e) => e.fmt(f),
+        }
+    }
+}
+impl From<rustpython_codegen::error::CodegenErrorType> for CompileErrorType {
+    fn from(source: rustpython_codegen::error::CodegenErrorType) -> Self {
+        CompileErrorType::Codegen(source)
+    }
+}
+impl From<parser::ParseErrorType> for CompileErrorType {
+    fn from(source: parser::ParseErrorType) -> Self {
+        CompileErrorType::Parse(source)
+    }
 }
 
 pub type CompileError = rustpython_compiler_core::CompileError<CompileErrorType>;
diff --git a/vm/Cargo.toml b/vm/Cargo.toml
index 841b5c5191..48d43e873a 100644
--- a/vm/Cargo.toml
+++ b/vm/Cargo.toml
@@ -21,6 +21,7 @@ compiler = ["parser", "codegen", "rustpython-compiler"]
 ast = ["rustpython-ast"]
 codegen = ["rustpython-codegen", "ast"]
 parser = ["rustpython-parser", "ast"]
+serde = ["dep:serde"]
 
 [dependencies]
 rustpython-compiler = { path = "../compiler", optional = true, version = "0.2.0" }
@@ -47,8 +48,8 @@ itertools = { workspace = true }
 libc = { workspace = true }
 log = { workspace = true }
 nix = { workspace = true }
-num-bigint = { workspace = true, features = ["serde"] }
-num-complex = { workspace = true, features = ["serde"] }
+num-bigint = { workspace = true }
+num-complex = { workspace = true }
 num-integer = { workspace = true }
 num-rational = { workspace = true }
 num-traits = { workspace = true }
@@ -57,7 +58,7 @@ once_cell = { workspace = true }
 parking_lot = { workspace = true }
 paste = { workspace = true }
 rand = { workspace = true }
-serde = { workspace = true, features = ["derive"] }
+serde = { workspace = true, optional = true }
 static_assertions = { workspace = true }
 thiserror = { workspace = true }
 thread_local = { workspace = true }
diff --git a/vm/src/builtins/code.rs b/vm/src/builtins/code.rs
index 2258af473f..fc987ea001 100644
--- a/vm/src/builtins/code.rs
+++ b/vm/src/builtins/code.rs
@@ -17,15 +17,15 @@ use std::{borrow::Borrow, fmt, ops::Deref};
 #[derive(FromArgs)]
 pub struct ReplaceArgs {
     #[pyarg(named, optional)]
-    co_posonlyargcount: OptionalArg<usize>,
+    co_posonlyargcount: OptionalArg<u32>,
     #[pyarg(named, optional)]
-    co_argcount: OptionalArg<usize>,
+    co_argcount: OptionalArg<u32>,
     #[pyarg(named, optional)]
-    co_kwonlyargcount: OptionalArg<usize>,
+    co_kwonlyargcount: OptionalArg<u32>,
     #[pyarg(named, optional)]
     co_filename: OptionalArg<PyStrRef>,
     #[pyarg(named, optional)]
-    co_firstlineno: OptionalArg<usize>,
+    co_firstlineno: OptionalArg<u32>,
     #[pyarg(named, optional)]
     co_consts: OptionalArg<Vec<PyObjectRef>>,
     #[pyarg(named, optional)]
@@ -39,6 +39,7 @@ pub struct ReplaceArgs {
 }
 
 #[derive(Clone)]
+#[repr(transparent)]
 pub struct Literal(PyObjectRef);
 
 impl Borrow<PyObject> for Literal {
@@ -77,9 +78,11 @@ fn borrow_obj_constant(obj: &PyObject) -> BorrowedConstant<Literal> {
             BorrowedConstant::Code { code: &c.code }
         }
         ref t @ super::tuple::PyTuple => {
-            BorrowedConstant::Tuple {
-                elements: Box::new(t.iter().map(|o| borrow_obj_constant(o))),
-            }
+            let elements = t.as_slice();
+            // SAFETY: Literal is repr(transparent) over PyObjectRef, and a Literal tuple only ever
+            //         has other literals as elements
+            let elements = unsafe { &*(elements as *const [PyObjectRef] as *const [Literal]) };
+            BorrowedConstant::Tuple { elements }
         }
         super::singletons::PyNone => BorrowedConstant::None,
         super::slice::PyEllipsis => BorrowedConstant::Ellipsis,
@@ -117,8 +120,8 @@ impl ConstantBag for PyObjBag<'_> {
             }
             bytecode::BorrowedConstant::Tuple { elements } => {
                 let elements = elements
-                    .into_iter()
-                    .map(|constant| self.make_constant(constant).0)
+                    .iter()
+                    .map(|constant| self.make_constant(constant.borrow_constant()).0)
                     .collect();
                 ctx.new_tuple(elements).into()
             }
@@ -131,6 +134,18 @@ impl ConstantBag for PyObjBag<'_> {
     fn make_name(&self, name: &str) -> &'static PyStrInterned {
         self.0.intern_str(name)
     }
+
+    fn make_int(&self, value: num_bigint::BigInt) -> Self::Constant {
+        Literal(self.0.new_int(value).into())
+    }
+
+    fn make_tuple(&self, elements: impl Iterator<Item = Self::Constant>) -> Self::Constant {
+        Literal(self.0.new_tuple(elements.map(|lit| lit.0).collect()).into())
+    }
+
+    fn make_code(&self, code: CodeObject) -> Self::Constant {
+        Literal(self.0.new_code(code).into())
+    }
 }
 
 pub type CodeObject = bytecode::CodeObject<Literal>;
@@ -205,12 +220,12 @@ impl PyRef<PyCode> {
 
     #[pygetset]
     fn co_posonlyargcount(self) -> usize {
-        self.code.posonlyarg_count
+        self.code.posonlyarg_count as usize
     }
 
     #[pygetset]
     fn co_argcount(self) -> usize {
-        self.code.arg_count
+        self.code.arg_count as usize
     }
 
     #[pygetset]
@@ -237,12 +252,12 @@ impl PyRef<PyCode> {
 
     #[pygetset]
     fn co_firstlineno(self) -> usize {
-        self.code.first_line_number
+        self.code.first_line_number as usize
     }
 
     #[pygetset]
     fn co_kwonlyargcount(self) -> usize {
-        self.code.kwonlyarg_count
+        self.code.kwonlyarg_count as usize
     }
 
     #[pygetset]
diff --git a/vm/src/builtins/complex.rs b/vm/src/builtins/complex.rs
index 3f86b48d19..3aaa4d6971 100644
--- a/vm/src/builtins/complex.rs
+++ b/vm/src/builtins/complex.rs
@@ -28,6 +28,12 @@ pub struct PyComplex {
     value: Complex64,
 }
 
+impl PyComplex {
+    pub fn to_complex64(self) -> Complex64 {
+        self.value
+    }
+}
+
 impl PyPayload for PyComplex {
     fn class(vm: &VirtualMachine) -> &'static Py<PyType> {
         vm.ctx.types.complex_type
diff --git a/vm/src/builtins/dict.rs b/vm/src/builtins/dict.rs
index 7fd8cac5d1..5c9c6fcded 100644
--- a/vm/src/builtins/dict.rs
+++ b/vm/src/builtins/dict.rs
@@ -609,43 +609,83 @@ impl Py<PyDict> {
 // Implement IntoIterator so that we can easily iterate dictionaries from rust code.
 impl IntoIterator for PyDictRef {
     type Item = (PyObjectRef, PyObjectRef);
-    type IntoIter = DictIter;
+    type IntoIter = DictIntoIter;
+
+    fn into_iter(self) -> Self::IntoIter {
+        DictIntoIter::new(self)
+    }
+}
+
+impl<'a> IntoIterator for &'a PyDictRef {
+    type Item = (PyObjectRef, PyObjectRef);
+    type IntoIter = DictIter<'a>;
 
     fn into_iter(self) -> Self::IntoIter {
         DictIter::new(self)
     }
 }
 
-impl IntoIterator for &PyDictRef {
+impl<'a> IntoIterator for &'a Py<PyDict> {
     type Item = (PyObjectRef, PyObjectRef);
-    type IntoIter = DictIter;
+    type IntoIter = DictIter<'a>;
 
     fn into_iter(self) -> Self::IntoIter {
-        DictIter::new(self.clone())
+        DictIter::new(self)
     }
 }
 
-impl IntoIterator for &Py<PyDict> {
+impl<'a> IntoIterator for &'a PyDict {
     type Item = (PyObjectRef, PyObjectRef);
-    type IntoIter = DictIter;
+    type IntoIter = DictIter<'a>;
 
     fn into_iter(self) -> Self::IntoIter {
-        DictIter::new(self.to_owned())
+        DictIter::new(self)
     }
 }
 
-pub struct DictIter {
+pub struct DictIntoIter {
     dict: PyDictRef,
     position: usize,
 }
 
-impl DictIter {
-    pub fn new(dict: PyDictRef) -> DictIter {
+impl DictIntoIter {
+    pub fn new(dict: PyDictRef) -> DictIntoIter {
+        DictIntoIter { dict, position: 0 }
+    }
+}
+
+impl Iterator for DictIntoIter {
+    type Item = (PyObjectRef, PyObjectRef);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let (position, key, value) = self.dict.entries.next_entry(self.position)?;
+        self.position = position;
+        Some((key, value))
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let l = self.len();
+        (l, Some(l))
+    }
+}
+impl ExactSizeIterator for DictIntoIter {
+    fn len(&self) -> usize {
+        self.dict.entries.len_from_entry_index(self.position)
+    }
+}
+
+pub struct DictIter<'a> {
+    dict: &'a PyDict,
+    position: usize,
+}
+
+impl<'a> DictIter<'a> {
+    pub fn new(dict: &'a PyDict) -> Self {
         DictIter { dict, position: 0 }
     }
 }
 
-impl Iterator for DictIter {
+impl Iterator for DictIter<'_> {
     type Item = (PyObjectRef, PyObjectRef);
 
     fn next(&mut self) -> Option<Self::Item> {
@@ -655,10 +695,15 @@ impl Iterator for DictIter {
     }
 
     fn size_hint(&self) -> (usize, Option<usize>) {
-        let l = self.dict.entries.len_from_entry_index(self.position);
+        let l = self.len();
         (l, Some(l))
     }
 }
+impl ExactSizeIterator for DictIter<'_> {
+    fn len(&self) -> usize {
+        self.dict.entries.len_from_entry_index(self.position)
+    }
+}
 
 #[pyclass]
 trait DictView: PyPayload + PyClassDef + Iterable
diff --git a/vm/src/builtins/float.rs b/vm/src/builtins/float.rs
index 9a7690fd49..6cb8fb32df 100644
--- a/vm/src/builtins/float.rs
+++ b/vm/src/builtins/float.rs
@@ -643,6 +643,7 @@ impl PyFloat {
 }
 
 // Retrieve inner float value:
+#[cfg(feature = "serde")]
 pub(crate) fn get_value(obj: &PyObject) -> f64 {
     obj.payload::<PyFloat>().unwrap().value
 }
diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs
index 8808eae987..2c41b5224a 100644
--- a/vm/src/builtins/function.rs
+++ b/vm/src/builtins/function.rs
@@ -66,8 +66,8 @@ impl PyFunction {
     ) -> PyResult<()> {
         let code = &*self.code;
         let nargs = func_args.args.len();
-        let nexpected_args = code.arg_count;
-        let total_args = code.arg_count + code.kwonlyarg_count;
+        let nexpected_args = code.arg_count as usize;
+        let total_args = code.arg_count as usize + code.kwonlyarg_count as usize;
         // let arg_names = self.code.arg_names();
 
         // This parses the arguments from args and kwargs into
@@ -131,7 +131,7 @@ impl PyFunction {
         // Handle keyword arguments
         for (name, value) in func_args.kwargs {
             // Check if we have a parameter with this name:
-            if let Some(pos) = argpos(code.posonlyarg_count..total_args, &name) {
+            if let Some(pos) = argpos(code.posonlyarg_count as usize..total_args, &name) {
                 let slot = &mut fastlocals[pos];
                 if slot.is_some() {
                     return Err(vm.new_type_error(format!(
@@ -143,7 +143,7 @@ impl PyFunction {
                 *slot = Some(value);
             } else if let Some(kwargs) = kwargs.as_ref() {
                 kwargs.set_item(&name, value, vm)?;
-            } else if argpos(0..code.posonlyarg_count, &name).is_some() {
+            } else if argpos(0..code.posonlyarg_count as usize, &name).is_some() {
                 posonly_passed_as_kwarg.push(name);
             } else {
                 return Err(vm.new_type_error(format!(
@@ -176,7 +176,7 @@ impl PyFunction {
             let defaults = get_defaults!().0.as_ref().map(|tup| tup.as_slice());
             let ndefs = defaults.map_or(0, |d| d.len());
 
-            let nrequired = code.arg_count - ndefs;
+            let nrequired = code.arg_count as usize - ndefs;
 
             // Given the number of defaults available, check all the arguments for which we
             // _don't_ have defaults; if any are missing, raise an exception
@@ -244,8 +244,8 @@ impl PyFunction {
             for (slot, kwarg) in fastlocals
                 .iter_mut()
                 .zip(&*code.varnames)
-                .skip(code.arg_count)
-                .take(code.kwonlyarg_count)
+                .skip(code.arg_count as usize)
+                .take(code.kwonlyarg_count as usize)
                 .filter(|(slot, _)| slot.is_none())
             {
                 if let Some(defaults) = &get_defaults!().1 {
diff --git a/vm/src/builtins/function/jitfunc.rs b/vm/src/builtins/function/jitfunc.rs
index 4c4bf09765..6b64f40f2b 100644
--- a/vm/src/builtins/function/jitfunc.rs
+++ b/vm/src/builtins/function/jitfunc.rs
@@ -118,7 +118,9 @@ fn get_jit_value(vm: &VirtualMachine, obj: &PyObject) -> Result<AbiValue, ArgsEr
             .map(AbiValue::Int)
             .ok_or(ArgsError::IntOverflow)
     } else if cls.is(vm.ctx.types.float_type) {
-        Ok(AbiValue::Float(float::get_value(obj)))
+        Ok(AbiValue::Float(
+            obj.downcast_ref::<float::PyFloat>().unwrap().to_f64(),
+        ))
     } else if cls.is(vm.ctx.types.bool_type) {
         Ok(AbiValue::Bool(bool_::get_value(obj)))
     } else {
@@ -141,7 +143,7 @@ pub(crate) fn get_jit_args<'a>(
     let nargs = func_args.args.len();
     let arg_names = func.code.arg_names();
 
-    if nargs > func.code.arg_count || nargs < func.code.posonlyarg_count {
+    if nargs > func.code.arg_count as usize || nargs < func.code.posonlyarg_count as usize {
         return Err(ArgsError::WrongNumberOfArgs);
     }
 
@@ -160,7 +162,7 @@ pub(crate) fn get_jit_args<'a>(
             }
             jit_args.set(arg_idx, get_jit_value(vm, value)?)?;
         } else if let Some(kwarg_idx) = arg_pos(arg_names.kwonlyargs, name) {
-            let arg_idx = kwarg_idx + func.code.arg_count;
+            let arg_idx = kwarg_idx + func.code.arg_count as usize;
             if jit_args.is_set(arg_idx) {
                 return Err(ArgsError::ArgPassedMultipleTimes);
             }
@@ -175,7 +177,7 @@ pub(crate) fn get_jit_args<'a>(
     // fill in positional defaults
     if let Some(defaults) = defaults {
         for (i, default) in defaults.iter().enumerate() {
-            let arg_idx = i + func.code.arg_count - defaults.len();
+            let arg_idx = i + func.code.arg_count as usize - defaults.len();
             if !jit_args.is_set(arg_idx) {
                 jit_args.set(arg_idx, get_jit_value(vm, default)?)?;
             }
@@ -185,7 +187,7 @@ pub(crate) fn get_jit_args<'a>(
     // fill in keyword only defaults
     if let Some(kw_only_defaults) = kwdefaults {
         for (i, name) in arg_names.kwonlyargs.iter().enumerate() {
-            let arg_idx = i + func.code.arg_count;
+            let arg_idx = i + func.code.arg_count as usize;
             if !jit_args.is_set(arg_idx) {
                 let default = kw_only_defaults
                     .get_item(&**name, vm)
diff --git a/vm/src/builtins/traceback.rs b/vm/src/builtins/traceback.rs
index 0af35f9f5c..f645f3ffbc 100644
--- a/vm/src/builtins/traceback.rs
+++ b/vm/src/builtins/traceback.rs
@@ -67,6 +67,7 @@ pub fn init(context: &Context) {
     PyTraceback::extend_class(context, context.types.traceback_type);
 }
 
+#[cfg(feature = "serde")]
 impl serde::Serialize for PyTraceback {
     fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
         use serde::ser::SerializeStruct;
diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs
index 3abf70d12a..5174301ac0 100644
--- a/vm/src/exceptions.rs
+++ b/vm/src/exceptions.rs
@@ -19,7 +19,6 @@ use itertools::Itertools;
 use std::{
     collections::HashSet,
     io::{self, BufRead, BufReader},
-    ops::Deref,
 };
 
 impl std::fmt::Debug for PyBaseException {
@@ -958,22 +957,26 @@ fn system_exit_code(exc: PyBaseExceptionRef) -> Option<PyObjectRef> {
     })
 }
 
+#[cfg(feature = "serde")]
 pub struct SerializeException<'vm, 's> {
     vm: &'vm VirtualMachine,
     exc: &'s PyBaseExceptionRef,
 }
 
+#[cfg(feature = "serde")]
 impl<'vm, 's> SerializeException<'vm, 's> {
     pub fn new(vm: &'vm VirtualMachine, exc: &'s PyBaseExceptionRef) -> Self {
         SerializeException { vm, exc }
     }
 }
 
+#[cfg(feature = "serde")]
 pub struct SerializeExceptionOwned<'vm> {
     vm: &'vm VirtualMachine,
     exc: PyBaseExceptionRef,
 }
 
+#[cfg(feature = "serde")]
 impl serde::Serialize for SerializeExceptionOwned<'_> {
     fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
         let Self { vm, exc } = self;
@@ -981,12 +984,13 @@ impl serde::Serialize for SerializeExceptionOwned<'_> {
     }
 }
 
+#[cfg(feature = "serde")]
 impl serde::Serialize for SerializeException<'_, '_> {
     fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
         use serde::ser::*;
 
         let mut struc = s.serialize_struct("PyBaseException", 7)?;
-        struc.serialize_field("exc_type", self.exc.class().name().deref())?;
+        struc.serialize_field("exc_type", &*self.exc.class().name())?;
         let tbs = {
             struct Tracebacks(PyTracebackRef);
             impl serde::Serialize for Tracebacks {
@@ -1111,8 +1115,6 @@ pub(super) mod types {
         PyObjectRef, PyRef, PyResult, VirtualMachine,
     };
     use crossbeam_utils::atomic::AtomicCell;
-    #[cfg_attr(target_arch = "wasm32", allow(unused_imports))]
-    use std::ops::Deref;
 
     // This module is designed to be used as `use builtins::*;`.
     // Do not add any pub symbols not included in builtins module.
@@ -1332,7 +1334,7 @@ pub(super) mod types {
         // We need this method, because of how `CPython` copies `init`
         // from `BaseException` in `SimpleExtendsException` macro.
         // See: `BaseException_new`
-        if cls.name().deref() == vm.ctx.exceptions.os_error.name().deref() {
+        if *cls.name() == *vm.ctx.exceptions.os_error.name() {
             match os_error_optional_new(args.args.to_vec(), vm) {
                 Some(error) => error.to_pyresult(vm),
                 None => PyBaseException::slot_new(cls, args, vm),
diff --git a/vm/src/frame.rs b/vm/src/frame.rs
index 1c6025a75b..868f3a0e20 100644
--- a/vm/src/frame.rs
+++ b/vm/src/frame.rs
@@ -513,10 +513,17 @@ impl ExecutingFrame<'_> {
                 Ok(None)
             }
             bytecode::Instruction::ImportName { idx } => {
-                self.import(vm, Some(self.code.names[idx.get(arg) as usize].to_owned()))
+                self.import(vm, Some(self.code.names[idx.get(arg) as usize].to_owned()))?;
+                Ok(None)
+            }
+            bytecode::Instruction::ImportNameless => {
+                self.import(vm, None)?;
+                Ok(None)
+            }
+            bytecode::Instruction::ImportStar => {
+                self.import_star(vm)?;
+                Ok(None)
             }
-            bytecode::Instruction::ImportNameless => self.import(vm, None),
-            bytecode::Instruction::ImportStar => self.import_star(vm),
             bytecode::Instruction::ImportFrom { idx } => {
                 let obj = self.import_from(vm, idx.get(arg))?;
                 self.push_value(obj);
@@ -1092,7 +1099,7 @@ impl ExecutingFrame<'_> {
     }
 
     #[cfg_attr(feature = "flame-it", flame("Frame"))]
-    fn import(&mut self, vm: &VirtualMachine, module: Option<PyStrRef>) -> FrameResult {
+    fn import(&mut self, vm: &VirtualMachine, module: Option<PyStrRef>) -> PyResult<()> {
         let module = module.unwrap_or_else(|| vm.ctx.empty_str.clone());
         let from_list = <Option<PyTupleTyped<PyStrRef>>>::try_from_object(vm, self.pop_value())?;
         let level = usize::try_from_object(vm, self.pop_value())?;
@@ -1100,7 +1107,7 @@ impl ExecutingFrame<'_> {
         let module = vm.import(module, from_list, level)?;
 
         self.push_value(module);
-        Ok(None)
+        Ok(())
     }
 
     #[cfg_attr(feature = "flame-it", flame("Frame"))]
@@ -1127,7 +1134,7 @@ impl ExecutingFrame<'_> {
     }
 
     #[cfg_attr(feature = "flame-it", flame("Frame"))]
-    fn import_star(&mut self, vm: &VirtualMachine) -> FrameResult {
+    fn import_star(&mut self, vm: &VirtualMachine) -> PyResult<()> {
         let module = self.pop_value();
 
         // Grab all the names from the module and put them in the context
@@ -1150,7 +1157,7 @@ impl ExecutingFrame<'_> {
                 }
             }
         }
-        Ok(None)
+        Ok(())
     }
 
     /// Unwind blocks.
diff --git a/vm/src/lib.rs b/vm/src/lib.rs
index 0fb964b942..b6b99efcd4 100644
--- a/vm/src/lib.rs
+++ b/vm/src/lib.rs
@@ -64,6 +64,7 @@ pub mod object;
 pub mod prelude;
 pub mod protocol;
 pub mod py_io;
+#[cfg(feature = "serde")]
 pub mod py_serde;
 pub mod readline;
 pub mod recursion;
diff --git a/vm/src/macros.rs b/vm/src/macros.rs
index 21db2e40eb..a31cf83c62 100644
--- a/vm/src/macros.rs
+++ b/vm/src/macros.rs
@@ -144,8 +144,8 @@ macro_rules! match_class {
     };
     (match ($obj:expr) { ref $binding:ident @ $class:ty => $expr:expr, $($rest:tt)* }) => {
         match $obj.payload::<$class>() {
-            Some($binding) => $expr,
-            None => $crate::match_class!(match ($obj) { $($rest)* }),
+            ::std::option::Option::Some($binding) => $expr,
+            ::std::option::Option::None => $crate::match_class!(match ($obj) { $($rest)* }),
         }
     };
 
diff --git a/vm/src/stdlib/marshal.rs b/vm/src/stdlib/marshal.rs
index 8133117b1e..db071243ea 100644
--- a/vm/src/stdlib/marshal.rs
+++ b/vm/src/stdlib/marshal.rs
@@ -2,207 +2,90 @@ pub(crate) use decl::make_module;
 
 #[pymodule(name = "marshal")]
 mod decl {
+    use crate::builtins::code::{CodeObject, Literal, PyObjBag};
+    use crate::class::StaticType;
     use crate::{
         builtins::{
-            PyBaseExceptionRef, PyByteArray, PyBytes, PyCode, PyDict, PyFloat, PyFrozenSet, PyInt,
-            PyList, PySet, PyStr, PyTuple,
+            PyBool, PyByteArray, PyBytes, PyCode, PyComplex, PyDict, PyEllipsis, PyFloat,
+            PyFrozenSet, PyInt, PyList, PyNone, PySet, PyStopIteration, PyStr, PyTuple,
         },
-        bytecode,
         convert::ToPyObject,
         function::{ArgBytesLike, OptionalArg},
         object::AsObject,
         protocol::PyBuffer,
         PyObjectRef, PyResult, TryFromObject, VirtualMachine,
     };
-    use num_bigint::{BigInt, Sign};
+    use num_bigint::BigInt;
+    use num_complex::Complex64;
     use num_traits::Zero;
-
-    #[repr(u8)]
-    enum Type {
-        // Null = b'0',
-        None = b'N',
-        False = b'F',
-        True = b'T',
-        StopIter = b'S',
-        Ellipsis = b'.',
-        Int = b'i',
-        Float = b'g',
-        // Complex = b'y',
-        // Long = b'l',  // i32
-        Bytes = b's', // = TYPE_STRING
-        // Interned = b't',
-        // Ref = b'r',
-        Tuple = b'(',
-        List = b'[',
-        Dict = b'{',
-        Code = b'c',
-        Unicode = b'u',
-        // Unknown = b'?',
-        Set = b'<',
-        FrozenSet = b'>',
-        Ascii = b'a',
-        // AsciiInterned = b'A',
-        // SmallTuple = b')',
-        // ShortAscii = b'z',
-        // ShortAsciiInterned = b'Z',
-    }
-    // const FLAG_REF: u8 = b'\x80';
-
-    impl TryFrom<u8> for Type {
-        type Error = u8;
-        fn try_from(value: u8) -> Result<Self, u8> {
-            use Type::*;
-            Ok(match value {
-                // b'0' => Null,
-                b'N' => None,
-                b'F' => False,
-                b'T' => True,
-                b'S' => StopIter,
-                b'.' => Ellipsis,
-                b'i' => Int,
-                b'g' => Float,
-                // b'y' => Complex,
-                // b'l' => Long,
-                b's' => Bytes,
-                // b't' => Interned,
-                // b'r' => Ref,
-                b'(' => Tuple,
-                b'[' => List,
-                b'{' => Dict,
-                b'c' => Code,
-                b'u' => Unicode,
-                // b'?' => Unknown,
-                b'<' => Set,
-                b'>' => FrozenSet,
-                b'a' => Ascii,
-                // b'A' => AsciiInterned,
-                // b')' => SmallTuple,
-                // b'z' => ShortAscii,
-                // b'Z' => ShortAsciiInterned,
-                c => return Err(c),
-            })
-        }
-    }
+    use rustpython_compiler_core::marshal;
 
     #[pyattr(name = "version")]
-    const VERSION: u32 = 4;
+    use marshal::FORMAT_VERSION;
 
-    fn too_short_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
-        vm.new_exception_msg(
-            vm.ctx.exceptions.eof_error.to_owned(),
-            "marshal data too short".to_owned(),
-        )
-    }
+    pub struct DumpError;
 
-    /// Dumps a sequence of objects into binary vector.
-    fn dump_seq(
-        buf: &mut Vec<u8>,
-        iter: std::slice::Iter<PyObjectRef>,
-        vm: &VirtualMachine,
-    ) -> PyResult<()> {
-        write_size(buf, iter.len(), vm)?;
-        // For each element, dump into binary, then add its length and value.
-        for element in iter {
-            dump_obj(buf, element.clone(), vm)?;
-        }
-        Ok(())
-    }
-
-    /// Dumping helper function to turn a value into bytes.
-    fn dump_obj(buf: &mut Vec<u8>, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
-        if vm.is_none(&value) {
-            buf.push(Type::None as u8);
-        } else if value.is(vm.ctx.exceptions.stop_iteration) {
-            buf.push(Type::StopIter as u8);
-        } else if value.is(&vm.ctx.ellipsis) {
-            buf.push(Type::Ellipsis as u8);
-        } else {
-            match_class!(match value {
-                pyint @ PyInt => {
-                    if pyint.class().is(vm.ctx.types.bool_type) {
-                        let typ = if pyint.as_bigint().is_zero() {
-                            Type::False
-                        } else {
-                            Type::True
-                        };
-                        buf.push(typ as u8);
+    impl marshal::Dumpable for PyObjectRef {
+        type Error = DumpError;
+        type Constant = Literal;
+        fn with_dump<R>(
+            &self,
+            f: impl FnOnce(marshal::DumpableValue<'_, Self>) -> R,
+        ) -> Result<R, Self::Error> {
+            use marshal::DumpableValue::*;
+            if self.is(PyStopIteration::static_type()) {
+                return Ok(f(StopIter));
+            }
+            let ret = match_class!(match self {
+                PyNone => f(None),
+                PyEllipsis => f(Ellipsis),
+                ref pyint @ PyInt => {
+                    if self.class().is(PyBool::static_type()) {
+                        f(Boolean(!pyint.as_bigint().is_zero()))
                     } else {
-                        buf.push(Type::Int as u8);
-                        let (sign, int_bytes) = pyint.as_bigint().to_bytes_le();
-                        let mut len = int_bytes.len() as i32;
-                        if sign == Sign::Minus {
-                            len = -len;
-                        }
-                        buf.extend(len.to_le_bytes());
-                        buf.extend(int_bytes);
+                        f(Integer(pyint.as_bigint()))
                     }
                 }
-                pyfloat @ PyFloat => {
-                    buf.push(Type::Float as u8);
-                    buf.extend(pyfloat.to_f64().to_le_bytes());
+                ref pyfloat @ PyFloat => {
+                    f(Float(pyfloat.to_f64()))
                 }
-                pystr @ PyStr => {
-                    buf.push(if pystr.is_ascii() {
-                        Type::Ascii
-                    } else {
-                        Type::Unicode
-                    } as u8);
-                    write_size(buf, pystr.as_str().len(), vm)?;
-                    buf.extend(pystr.as_str().as_bytes());
+                ref pycomplex @ PyComplex => {
+                    f(Complex(pycomplex.to_complex64()))
+                }
+                ref pystr @ PyStr => {
+                    f(Str(pystr.as_str()))
                 }
-                pylist @ PyList => {
-                    buf.push(Type::List as u8);
-                    let pylist_items = pylist.borrow_vec();
-                    dump_seq(buf, pylist_items.iter(), vm)?;
+                ref pylist @ PyList => {
+                    f(List(&pylist.borrow_vec()))
                 }
-                pyset @ PySet => {
-                    buf.push(Type::Set as u8);
+                ref pyset @ PySet => {
                     let elements = pyset.elements();
-                    dump_seq(buf, elements.iter(), vm)?;
+                    f(Set(&elements))
                 }
-                pyfrozen @ PyFrozenSet => {
-                    buf.push(Type::FrozenSet as u8);
+                ref pyfrozen @ PyFrozenSet => {
                     let elements = pyfrozen.elements();
-                    dump_seq(buf, elements.iter(), vm)?;
+                    f(Frozenset(&elements))
                 }
-                pytuple @ PyTuple => {
-                    buf.push(Type::Tuple as u8);
-                    dump_seq(buf, pytuple.iter(), vm)?;
+                ref pytuple @ PyTuple => {
+                    f(Tuple(pytuple.as_slice()))
                 }
-                pydict @ PyDict => {
-                    buf.push(Type::Dict as u8);
-                    write_size(buf, pydict.len(), vm)?;
-                    for (key, value) in pydict {
-                        dump_obj(buf, key, vm)?;
-                        dump_obj(buf, value, vm)?;
-                    }
-                }
-                bytes @ PyBytes => {
-                    buf.push(Type::Bytes as u8);
-                    let data = bytes.as_bytes();
-                    write_size(buf, data.len(), vm)?;
-                    buf.extend(data);
+                ref pydict @ PyDict => {
+                    let entries = pydict.into_iter().collect::<Vec<_>>();
+                    f(Dict(&entries))
                 }
-                bytes @ PyByteArray => {
-                    buf.push(Type::Bytes as u8);
-                    let data = bytes.borrow_buf();
-                    write_size(buf, data.len(), vm)?;
-                    buf.extend(&*data);
+                ref bytes @ PyBytes => {
+                    f(Bytes(bytes.as_bytes()))
                 }
-                co @ PyCode => {
-                    buf.push(Type::Code as u8);
-                    let bytes = co.code.map_clone_bag(&bytecode::BasicBag).to_bytes();
-                    write_size(buf, bytes.len(), vm)?;
-                    buf.extend(bytes);
+                ref bytes @ PyByteArray => {
+                    f(Bytes(&bytes.borrow_buf()))
                 }
-                _ => {
-                    return Err(vm.new_not_implemented_error(
-                        "TODO: not implemented yet or marshal unsupported type".to_owned(),
-                    ));
+                ref co @ PyCode => {
+                    f(Code(co))
                 }
-            })
+                _ => return Err(DumpError),
+            });
+            Ok(ret)
         }
-        Ok(())
     }
 
     #[pyfunction]
@@ -211,8 +94,16 @@ mod decl {
         _version: OptionalArg<i32>,
         vm: &VirtualMachine,
     ) -> PyResult<PyBytes> {
+        use marshal::Dumpable;
         let mut buf = Vec::new();
-        dump_obj(&mut buf, value, vm)?;
+        value
+            .with_dump(|val| marshal::serialize_value(&mut buf, val))
+            .unwrap_or_else(Err)
+            .map_err(|DumpError| {
+                vm.new_not_implemented_error(
+                    "TODO: not implemented yet or marshal unsupported type".to_owned(),
+                )
+            })?;
         Ok(PyBytes::from(buf))
     }
 
@@ -228,38 +119,83 @@ mod decl {
         Ok(())
     }
 
-    /// Safely convert usize to 4 le bytes
-    fn write_size(buf: &mut Vec<u8>, x: usize, vm: &VirtualMachine) -> PyResult<()> {
-        // For marshalling we want to convert lengths to bytes. To save space
-        // we limit the size to u32 to keep marshalling smaller.
-        let n = u32::try_from(x).map_err(|_| {
-            vm.new_value_error("Size exceeds 2^32 capacity for marshaling.".to_owned())
-        })?;
-        buf.extend(n.to_le_bytes());
-        Ok(())
-    }
+    #[derive(Copy, Clone)]
+    struct PyMarshalBag<'a>(&'a VirtualMachine);
 
-    /// Read the next 4 bytes of a slice, read as u32, pass as usize.
-    /// Returns the rest of buffer with the value.
-    fn read_size<'a>(buf: &'a [u8], vm: &VirtualMachine) -> PyResult<(usize, &'a [u8])> {
-        if buf.len() < 4 {
-            return Err(too_short_error(vm));
+    impl<'a> marshal::MarshalBag for PyMarshalBag<'a> {
+        type Value = PyObjectRef;
+        fn make_bool(&self, value: bool) -> Self::Value {
+            self.0.ctx.new_bool(value).into()
         }
-        let (u32_bytes, rest) = buf.split_at(4);
-        let length = u32::from_le_bytes(u32_bytes.try_into().unwrap());
-        Ok((length as usize, rest))
-    }
-
-    /// Reads a list (or tuple) from a buffer.
-    fn load_seq<'b>(buf: &'b [u8], vm: &VirtualMachine) -> PyResult<(Vec<PyObjectRef>, &'b [u8])> {
-        let (len, mut buf) = read_size(buf, vm)?;
-        let mut elements: Vec<PyObjectRef> = Vec::new();
-        for _ in 0..len {
-            let (element, rest) = load_obj(buf, vm)?;
-            buf = rest;
-            elements.push(element);
+        fn make_none(&self) -> Self::Value {
+            self.0.ctx.none()
+        }
+        fn make_ellipsis(&self) -> Self::Value {
+            self.0.ctx.ellipsis()
+        }
+        fn make_float(&self, value: f64) -> Self::Value {
+            self.0.ctx.new_float(value).into()
+        }
+        fn make_complex(&self, value: Complex64) -> Self::Value {
+            self.0.ctx.new_complex(value).into()
+        }
+        fn make_str(&self, value: &str) -> Self::Value {
+            self.0.ctx.new_str(value).into()
+        }
+        fn make_bytes(&self, value: &[u8]) -> Self::Value {
+            self.0.ctx.new_bytes(value.to_vec()).into()
+        }
+        fn make_int(&self, value: BigInt) -> Self::Value {
+            self.0.ctx.new_int(value).into()
+        }
+        fn make_tuple(&self, elements: impl Iterator<Item = Self::Value>) -> Self::Value {
+            self.0.ctx.new_tuple(elements.collect()).into()
+        }
+        fn make_code(&self, code: CodeObject) -> Self::Value {
+            self.0.ctx.new_code(code).into()
+        }
+        fn make_stop_iter(&self) -> Result<Self::Value, marshal::MarshalError> {
+            Ok(self.0.ctx.exceptions.stop_iteration.to_owned().into())
+        }
+        fn make_list(
+            &self,
+            it: impl Iterator<Item = Self::Value>,
+        ) -> Result<Self::Value, marshal::MarshalError> {
+            Ok(self.0.ctx.new_list(it.collect()).into())
+        }
+        fn make_set(
+            &self,
+            it: impl Iterator<Item = Self::Value>,
+        ) -> Result<Self::Value, marshal::MarshalError> {
+            let vm = self.0;
+            let set = PySet::new_ref(&vm.ctx);
+            for elem in it {
+                set.add(elem, vm).unwrap()
+            }
+            Ok(set.into())
+        }
+        fn make_frozenset(
+            &self,
+            it: impl Iterator<Item = Self::Value>,
+        ) -> Result<Self::Value, marshal::MarshalError> {
+            let vm = self.0;
+            Ok(PyFrozenSet::from_iter(vm, it).unwrap().to_pyobject(vm))
+        }
+        fn make_dict(
+            &self,
+            it: impl Iterator<Item = (Self::Value, Self::Value)>,
+        ) -> Result<Self::Value, marshal::MarshalError> {
+            let vm = self.0;
+            let dict = vm.ctx.new_dict();
+            for (k, v) in it {
+                dict.set_item(&*k, v, vm).unwrap()
+            }
+            Ok(dict.into())
+        }
+        type ConstantBag = PyObjBag<'a>;
+        fn constant_bag(self) -> Self::ConstantBag {
+            PyObjBag(&self.0.ctx)
         }
-        Ok((elements, buf))
     }
 
     #[pyfunction]
@@ -267,128 +203,21 @@ mod decl {
         let buf = pybuffer.as_contiguous().ok_or_else(|| {
             vm.new_buffer_error("Buffer provided to marshal.loads() is not contiguous".to_owned())
         })?;
-        let (obj, _) = load_obj(&buf, vm)?;
-        Ok(obj)
-    }
-
-    fn load_obj<'b>(buf: &'b [u8], vm: &VirtualMachine) -> PyResult<(PyObjectRef, &'b [u8])> {
-        let (type_indicator, buf) = buf.split_first().ok_or_else(|| too_short_error(vm))?;
-        let typ = Type::try_from(*type_indicator)
-            .map_err(|_| vm.new_value_error("bad marshal data (unknown type code)".to_owned()))?;
-        let (obj, buf) = match typ {
-            Type::True => (true.to_pyobject(vm), buf),
-            Type::False => (false.to_pyobject(vm), buf),
-            Type::None => (vm.ctx.none(), buf),
-            Type::StopIter => (
-                vm.ctx.exceptions.stop_iteration.to_owned().to_pyobject(vm),
-                buf,
+        marshal::deserialize_value(&mut &buf[..], PyMarshalBag(vm)).map_err(|e| match e {
+            marshal::MarshalError::Eof => vm.new_exception_msg(
+                vm.ctx.exceptions.eof_error.to_owned(),
+                "marshal data too short".to_owned(),
             ),
-            Type::Ellipsis => (vm.ctx.ellipsis(), buf),
-            Type::Int => {
-                if buf.len() < 4 {
-                    return Err(too_short_error(vm));
-                }
-                let (len_bytes, buf) = buf.split_at(4);
-                let len = i32::from_le_bytes(len_bytes.try_into().unwrap());
-                let (sign, len) = if len < 0 {
-                    (Sign::Minus, (-len) as usize)
-                } else {
-                    (Sign::Plus, len as usize)
-                };
-                if buf.len() < len {
-                    return Err(too_short_error(vm));
-                }
-                let (bytes, buf) = buf.split_at(len);
-                let int = BigInt::from_bytes_le(sign, bytes);
-                (int.to_pyobject(vm), buf)
+            marshal::MarshalError::InvalidBytecode => {
+                vm.new_value_error("Couldn't deserialize python bytecode".to_owned())
             }
-            Type::Float => {
-                if buf.len() < 8 {
-                    return Err(too_short_error(vm));
-                }
-                let (bytes, buf) = buf.split_at(8);
-                let number = f64::from_le_bytes(bytes.try_into().unwrap());
-                (vm.ctx.new_float(number).into(), buf)
+            marshal::MarshalError::InvalidUtf8 => {
+                vm.new_value_error("invalid utf8 in marshalled string".to_owned())
             }
-            Type::Ascii => {
-                let (len, buf) = read_size(buf, vm)?;
-                if buf.len() < len {
-                    return Err(too_short_error(vm));
-                }
-                let (bytes, buf) = buf.split_at(len);
-                let s = String::from_utf8(bytes.to_vec())
-                    .map_err(|_| vm.new_value_error("invalid utf8 data".to_owned()))?;
-                (s.to_pyobject(vm), buf)
-            }
-            Type::Unicode => {
-                let (len, buf) = read_size(buf, vm)?;
-                if buf.len() < len {
-                    return Err(too_short_error(vm));
-                }
-                let (bytes, buf) = buf.split_at(len);
-                let s = String::from_utf8(bytes.to_vec())
-                    .map_err(|_| vm.new_value_error("invalid utf8 data".to_owned()))?;
-                (s.to_pyobject(vm), buf)
-            }
-            Type::List => {
-                let (elements, buf) = load_seq(buf, vm)?;
-                (vm.ctx.new_list(elements).into(), buf)
-            }
-            Type::Set => {
-                let (elements, buf) = load_seq(buf, vm)?;
-                let set = PySet::new_ref(&vm.ctx);
-                for element in elements {
-                    set.add(element, vm)?;
-                }
-                (set.to_pyobject(vm), buf)
-            }
-            Type::FrozenSet => {
-                let (elements, buf) = load_seq(buf, vm)?;
-                let set = PyFrozenSet::from_iter(vm, elements.into_iter())?;
-                (set.to_pyobject(vm), buf)
-            }
-            Type::Tuple => {
-                let (elements, buf) = load_seq(buf, vm)?;
-                (vm.ctx.new_tuple(elements).into(), buf)
-            }
-            Type::Dict => {
-                let (len, mut buf) = read_size(buf, vm)?;
-                let dict = vm.ctx.new_dict();
-                for _ in 0..len {
-                    let (key, rest) = load_obj(buf, vm)?;
-                    let (value, rest) = load_obj(rest, vm)?;
-                    buf = rest;
-                    dict.set_item(key.as_object(), value, vm)?;
-                }
-                (dict.into(), buf)
-            }
-            Type::Bytes => {
-                // Following CPython, after marshaling, byte arrays are converted into bytes.
-                let (len, buf) = read_size(buf, vm)?;
-                if buf.len() < len {
-                    return Err(too_short_error(vm));
-                }
-                let (bytes, buf) = buf.split_at(len);
-                (vm.ctx.new_bytes(bytes.to_vec()).into(), buf)
-            }
-            Type::Code => {
-                // If prefix is not identifiable, assume CodeObject, error out if it doesn't match.
-                let (len, buf) = read_size(buf, vm)?;
-                if buf.len() < len {
-                    return Err(too_short_error(vm));
-                }
-                let (bytes, buf) = buf.split_at(len);
-                let code = bytecode::CodeObject::from_bytes(bytes).map_err(|e| match e {
-                    bytecode::CodeDeserializeError::Eof => vm.new_exception_msg(
-                        vm.ctx.exceptions.eof_error.to_owned(),
-                        "End of file while deserializing bytecode".to_owned(),
-                    ),
-                    _ => vm.new_value_error("Couldn't deserialize python bytecode".to_owned()),
-                })?;
-                (vm.ctx.new_code(code).into(), buf)
+            marshal::MarshalError::BadType => {
+                vm.new_value_error("bad marshal data (unknown type code)".to_owned())
             }
-        };
-        Ok((obj, buf))
+        })
     }
 
     #[pyfunction]
diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml
index 8a28f903f8..04af5b6f27 100644
--- a/wasm/lib/Cargo.toml
+++ b/wasm/lib/Cargo.toml
@@ -21,7 +21,7 @@ rustpython-parser = { path = "../../compiler/parser" }
 rustpython-pylib = { path = "../../pylib", default-features = false, optional = true }
 rustpython-stdlib = { path = "../../stdlib", default-features = false, optional = true }
 # make sure no threading! otherwise wasm build will fail
-rustpython-vm = { path = "../../vm", default-features = false, features = ["compiler", "encodings"] }
+rustpython-vm = { path = "../../vm", default-features = false, features = ["compiler", "encodings", "serde"] }
 
 serde = { workspace = true }
 


Test patch: