RustPython-Parser/core/src/bytecode.rs
2023-03-01 20:47:21 -06:00

1633 lines
50 KiB
Rust

//! Implement python as a virtual machine with bytecodes. This module
//! implements bytecode structure.
use crate::marshal::MarshalError;
use crate::{marshal, Location};
use bitflags::bitflags;
use itertools::Itertools;
use num_bigint::BigInt;
use num_complex::Complex64;
use std::marker::PhantomData;
use std::{collections::BTreeSet, fmt, hash, mem};
pub trait Constant: Sized {
type Name: AsRef<str>;
/// Transforms the given Constant to a BorrowedConstant
fn borrow_constant(&self) -> BorrowedConstant<Self>;
}
impl Constant for ConstantData {
type Name = String;
fn borrow_constant(&self) -> BorrowedConstant<Self> {
use BorrowedConstant::*;
match self {
ConstantData::Integer { value } => Integer { value },
ConstantData::Float { value } => Float { value: *value },
ConstantData::Complex { value } => Complex { value: *value },
ConstantData::Boolean { value } => Boolean { value: *value },
ConstantData::Str { value } => Str { value },
ConstantData::Bytes { value } => Bytes { value },
ConstantData::Code { code } => Code { code },
ConstantData::Tuple { elements } => Tuple { elements },
ConstantData::None => None,
ConstantData::Ellipsis => Ellipsis,
}
}
}
/// A Constant Bag
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;
}
#[derive(Clone, Copy)]
pub struct BasicBag;
impl ConstantBag for BasicBag {
type Constant = ConstantData;
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()
}
}
/// Primary container of a single code object. Each python function has
/// a codeobject. Also a module has a codeobject.
#[derive(Clone)]
pub struct CodeObject<C: Constant = ConstantData> {
pub instructions: Box<[CodeUnit]>,
pub locations: Box<[Location]>,
pub flags: CodeFlags,
pub posonlyarg_count: u32,
// Number of positional-only arguments
pub arg_count: u32,
pub kwonlyarg_count: u32,
pub source_path: C::Name,
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<[i32]>>,
pub constants: Box<[C]>,
pub names: Box<[C::Name]>,
pub varnames: Box<[C::Name]>,
pub cellvars: Box<[C::Name]>,
pub freevars: Box<[C::Name]>,
}
bitflags! {
pub struct CodeFlags: u16 {
const NEW_LOCALS = 0x01;
const IS_GENERATOR = 0x02;
const IS_COROUTINE = 0x04;
const HAS_VARARGS = 0x08;
const HAS_VARKEYWORDS = 0x10;
const IS_OPTIMIZED = 0x20;
}
}
impl CodeFlags {
pub const NAME_MAPPING: &'static [(&'static str, CodeFlags)] = &[
("GENERATOR", CodeFlags::IS_GENERATOR),
("COROUTINE", CodeFlags::IS_COROUTINE),
(
"ASYNC_GENERATOR",
Self::from_bits_truncate(Self::IS_GENERATOR.bits | Self::IS_COROUTINE.bits),
),
("VARARGS", CodeFlags::HAS_VARARGS),
("VARKEYWORDS", CodeFlags::HAS_VARKEYWORDS),
];
}
/// an opcode argument that may be extended by a prior ExtendedArg
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct OpArgByte(pub u8);
impl OpArgByte {
pub const fn null() -> Self {
OpArgByte(0)
}
}
impl fmt::Debug for OpArgByte {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
/// a full 32-bit oparg, including any possible ExtendedArg extension
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct OpArg(pub u32);
impl OpArg {
pub const fn null() -> Self {
OpArg(0)
}
/// Returns how many CodeUnits a instruction with this oparg will be encoded as
#[inline]
pub fn instr_size(self) -> usize {
(self.0 > 0xff) as usize + (self.0 > 0xff_ff) as usize + (self.0 > 0xff_ff_ff) as usize + 1
}
/// returns the arg split into any necessary ExtendedArg components (in big-endian order) and
/// the arg for the real opcode itself
#[inline(always)]
pub fn split(self) -> (impl ExactSizeIterator<Item = OpArgByte>, OpArgByte) {
let mut it = self
.0
.to_le_bytes()
.map(OpArgByte)
.into_iter()
.take(self.instr_size());
let lo = it.next().unwrap();
(it.rev(), lo)
}
}
#[derive(Default, Copy, Clone)]
#[repr(transparent)]
pub struct OpArgState {
state: u32,
}
impl OpArgState {
#[inline(always)]
pub fn get(&mut self, ins: CodeUnit) -> (Instruction, OpArg) {
let arg = self.extend(ins.arg);
if ins.op != Instruction::ExtendedArg {
self.reset();
}
(ins.op, arg)
}
#[inline(always)]
pub fn extend(&mut self, arg: OpArgByte) -> OpArg {
self.state = self.state << 8 | u32::from(arg.0);
OpArg(self.state)
}
#[inline(always)]
pub fn reset(&mut self) {
self.state = 0
}
}
pub trait OpArgType: Copy {
fn from_oparg(x: u32) -> Option<Self>;
fn to_oparg(self) -> u32;
}
impl OpArgType for u32 {
#[inline(always)]
fn from_oparg(x: u32) -> Option<Self> {
Some(x)
}
#[inline(always)]
fn to_oparg(self) -> u32 {
self
}
}
impl OpArgType for bool {
#[inline(always)]
fn from_oparg(x: u32) -> Option<Self> {
Some(x != 0)
}
#[inline(always)]
fn to_oparg(self) -> u32 {
self as u32
}
}
#[derive(Copy, Clone)]
pub struct Arg<T: OpArgType>(PhantomData<T>);
impl<T: OpArgType> Arg<T> {
#[inline]
pub fn marker() -> Self {
Arg(PhantomData)
}
#[inline]
pub fn new(arg: T) -> (Self, OpArg) {
(Self(PhantomData), OpArg(arg.to_oparg()))
}
#[inline]
pub fn new_single(arg: T) -> (Self, OpArgByte)
where
T: Into<u8>,
{
(Self(PhantomData), OpArgByte(arg.into()))
}
#[inline(always)]
pub fn get(self, arg: OpArg) -> T {
self.try_get(arg).unwrap()
}
#[inline(always)]
pub fn try_get(self, arg: OpArg) -> Option<T> {
T::from_oparg(arg.0)
}
#[inline(always)]
/// # Safety
/// T::from_oparg(self) must succeed
pub unsafe fn get_unchecked(self, arg: OpArg) -> T {
match T::from_oparg(arg.0) {
Some(t) => t,
None => std::hint::unreachable_unchecked(),
}
}
}
impl<T: OpArgType> PartialEq for Arg<T> {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl<T: OpArgType> Eq for Arg<T> {}
impl<T: OpArgType> fmt::Debug for Arg<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Arg<{}>", std::any::type_name::<T>())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
#[repr(transparent)]
// XXX: if you add a new instruction that stores a Label, make sure to add it in
// Instruction::label_arg
pub struct Label(pub u32);
impl OpArgType for Label {
#[inline(always)]
fn from_oparg(x: u32) -> Option<Self> {
Some(Label(x))
}
#[inline(always)]
fn to_oparg(self) -> u32 {
self.0
}
}
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
/// 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 OpArgType for ConversionFlag {
fn to_oparg(self) -> u32 {
self as u32
}
fn from_oparg(x: u32) -> Option<Self> {
Some(match u8::try_from(x).ok()? {
0 => Self::None,
b's' => Self::Str,
b'a' => Self::Ascii,
b'r' => Self::Repr,
_ => return None,
})
}
}
impl TryFrom<usize> for ConversionFlag {
type Error = usize;
fn try_from(b: usize) -> Result<Self, Self::Error> {
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)]
#[repr(u8)]
pub enum RaiseKind {
Reraise,
Raise,
RaiseCause,
}
impl OpArgType for RaiseKind {
fn to_oparg(self) -> u32 {
self as u32
}
fn from_oparg(x: u32) -> Option<Self> {
Some(match x {
0 => Self::Reraise,
1 => Self::Raise,
2 => Self::RaiseCause,
_ => return None,
})
}
}
pub type NameIdx = u32;
/// A Single bytecode instruction.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum Instruction {
/// Importing by name
ImportName {
idx: Arg<NameIdx>,
},
/// Importing without name
ImportNameless,
/// Import *
ImportStar,
/// from ... import ...
ImportFrom {
idx: Arg<NameIdx>,
},
LoadFast(Arg<NameIdx>),
LoadNameAny(Arg<NameIdx>),
LoadGlobal(Arg<NameIdx>),
LoadDeref(Arg<NameIdx>),
LoadClassDeref(Arg<NameIdx>),
StoreFast(Arg<NameIdx>),
StoreLocal(Arg<NameIdx>),
StoreGlobal(Arg<NameIdx>),
StoreDeref(Arg<NameIdx>),
DeleteFast(Arg<NameIdx>),
DeleteLocal(Arg<NameIdx>),
DeleteGlobal(Arg<NameIdx>),
DeleteDeref(Arg<NameIdx>),
LoadClosure(Arg<NameIdx>),
Subscript,
StoreSubscript,
DeleteSubscript,
StoreAttr {
idx: Arg<NameIdx>,
},
DeleteAttr {
idx: Arg<NameIdx>,
},
LoadConst {
/// index into constants vec
idx: Arg<u32>,
},
UnaryOperation {
op: Arg<UnaryOperator>,
},
BinaryOperation {
op: Arg<BinaryOperator>,
},
BinaryOperationInplace {
op: Arg<BinaryOperator>,
},
LoadAttr {
idx: Arg<NameIdx>,
},
TestOperation {
op: Arg<TestOperator>,
},
CompareOperation {
op: Arg<ComparisonOperator>,
},
Pop,
Rotate2,
Rotate3,
Duplicate,
Duplicate2,
GetIter,
Continue {
target: Arg<Label>,
},
Break {
target: Arg<Label>,
},
Jump {
target: Arg<Label>,
},
/// Pop the top of the stack, and jump if this value is true.
JumpIfTrue {
target: Arg<Label>,
},
/// Pop the top of the stack, and jump if this value is false.
JumpIfFalse {
target: Arg<Label>,
},
/// Peek at the top of the stack, and jump if this value is true.
/// Otherwise, pop top of stack.
JumpIfTrueOrPop {
target: Arg<Label>,
},
/// Peek at the top of the stack, and jump if this value is false.
/// Otherwise, pop top of stack.
JumpIfFalseOrPop {
target: Arg<Label>,
},
MakeFunction(Arg<MakeFunctionFlags>),
CallFunctionPositional {
nargs: Arg<u32>,
},
CallFunctionKeyword {
nargs: Arg<u32>,
},
CallFunctionEx {
has_kwargs: Arg<bool>,
},
LoadMethod {
idx: Arg<NameIdx>,
},
CallMethodPositional {
nargs: Arg<u32>,
},
CallMethodKeyword {
nargs: Arg<u32>,
},
CallMethodEx {
has_kwargs: Arg<bool>,
},
ForIter {
target: Arg<Label>,
},
ReturnValue,
YieldValue,
YieldFrom,
SetupAnnotation,
SetupLoop,
/// Setup a finally handler, which will be called whenever one of this events occurs:
/// - the block is popped
/// - the function returns
/// - an exception is returned
SetupFinally {
handler: Arg<Label>,
},
/// Enter a finally block, without returning, excepting, just because we are there.
EnterFinally,
/// Marker bytecode for the end of a finally sequence.
/// When this bytecode is executed, the eval loop does one of those things:
/// - Continue at a certain bytecode position
/// - Propagate the exception
/// - Return from a function
/// - Do nothing at all, just continue
EndFinally,
SetupExcept {
handler: Arg<Label>,
},
SetupWith {
end: Arg<Label>,
},
WithCleanupStart,
WithCleanupFinish,
PopBlock,
Raise {
kind: Arg<RaiseKind>,
},
BuildString {
size: Arg<u32>,
},
BuildTuple {
size: Arg<u32>,
},
BuildTupleUnpack {
size: Arg<u32>,
},
BuildList {
size: Arg<u32>,
},
BuildListUnpack {
size: Arg<u32>,
},
BuildSet {
size: Arg<u32>,
},
BuildSetUnpack {
size: Arg<u32>,
},
BuildMap {
size: Arg<u32>,
},
BuildMapForCall {
size: Arg<u32>,
},
DictUpdate,
BuildSlice {
/// whether build a slice with a third step argument
step: Arg<bool>,
},
ListAppend {
i: Arg<u32>,
},
SetAdd {
i: Arg<u32>,
},
MapAdd {
i: Arg<u32>,
},
PrintExpr,
LoadBuildClass,
UnpackSequence {
size: Arg<u32>,
},
UnpackEx {
args: Arg<UnpackExArgs>,
},
FormatValue {
conversion: Arg<ConversionFlag>,
},
PopException,
Reverse {
amount: Arg<u32>,
},
GetAwaitable,
BeforeAsyncWith,
SetupAsyncWith {
end: Arg<Label>,
},
GetAIter,
GetANext,
EndAsyncFor,
ExtendedArg,
}
const _: () = assert!(mem::size_of::<Instruction>() == 1);
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,
}
const _: () = assert!(mem::size_of::<CodeUnit>() == 2);
impl CodeUnit {
pub fn new(op: Instruction, arg: OpArgByte) -> Self {
Self { op, arg }
}
}
use self::Instruction::*;
bitflags! {
pub struct MakeFunctionFlags: u8 {
const CLOSURE = 0x01;
const ANNOTATIONS = 0x02;
const KW_ONLY_DEFAULTS = 0x04;
const DEFAULTS = 0x08;
}
}
impl OpArgType for MakeFunctionFlags {
#[inline(always)]
fn from_oparg(x: u32) -> Option<Self> {
Some(unsafe { MakeFunctionFlags::from_bits_unchecked(x as u8) })
}
#[inline(always)]
fn to_oparg(self) -> u32 {
self.bits().into()
}
}
/// A Constant (which usually encapsulates data within it)
///
/// # Examples
/// ```
/// use rustpython_compiler_core::ConstantData;
/// let a = ConstantData::Float {value: 120f64};
/// let b = ConstantData::Boolean {value: false};
/// assert_ne!(a, b);
/// ```
#[derive(Debug, Clone)]
pub enum ConstantData {
Tuple { elements: Vec<ConstantData> },
Integer { value: BigInt },
Float { value: f64 },
Complex { value: Complex64 },
Boolean { value: bool },
Str { value: String },
Bytes { value: Vec<u8> },
Code { code: Box<CodeObject> },
None,
Ellipsis,
}
impl PartialEq for ConstantData {
fn eq(&self, other: &Self) -> bool {
use ConstantData::*;
match (self, other) {
(Integer { value: a }, Integer { value: b }) => a == b,
// we want to compare floats *by actual value* - if we have the *exact same* float
// already in a constant cache, we want to use that
(Float { value: a }, Float { value: b }) => a.to_bits() == b.to_bits(),
(Complex { value: a }, Complex { value: b }) => {
a.re.to_bits() == b.re.to_bits() && a.im.to_bits() == b.im.to_bits()
}
(Boolean { value: a }, Boolean { value: b }) => a == b,
(Str { value: a }, Str { value: b }) => a == b,
(Bytes { value: a }, Bytes { value: b }) => a == b,
(Code { code: a }, Code { code: b }) => std::ptr::eq(a.as_ref(), b.as_ref()),
(Tuple { elements: a }, Tuple { elements: b }) => a == b,
(None, None) => true,
(Ellipsis, Ellipsis) => true,
_ => false,
}
}
}
impl Eq for ConstantData {}
impl hash::Hash for ConstantData {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
use ConstantData::*;
mem::discriminant(self).hash(state);
match self {
Integer { value } => value.hash(state),
Float { value } => value.to_bits().hash(state),
Complex { value } => {
value.re.to_bits().hash(state);
value.im.to_bits().hash(state);
}
Boolean { value } => value.hash(state),
Str { value } => value.hash(state),
Bytes { value } => value.hash(state),
Code { code } => std::ptr::hash(code.as_ref(), state),
Tuple { elements } => elements.hash(state),
None => {}
Ellipsis => {}
}
}
}
/// A borrowed Constant
pub enum BorrowedConstant<'a, C: Constant> {
Integer { value: &'a BigInt },
Float { value: f64 },
Complex { value: Complex64 },
Boolean { value: bool },
Str { value: &'a str },
Bytes { value: &'a [u8] },
Code { code: &'a CodeObject<C> },
Tuple { elements: &'a [C] },
None,
Ellipsis,
}
impl<C: Constant> Copy for BorrowedConstant<'_, C> {}
impl<C: Constant> Clone for BorrowedConstant<'_, C> {
fn clone(&self) -> Self {
*self
}
}
impl<C: Constant> BorrowedConstant<'_, C> {
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" })
}
BorrowedConstant::Str { value } => write!(f, "{value:?}"),
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 {
if first {
first = false
} else {
write!(f, ", ")?;
}
c.borrow_constant().fmt_display(f)?;
}
write!(f, ")")
}
BorrowedConstant::None => write!(f, "None"),
BorrowedConstant::Ellipsis => write!(f, "..."),
}
}
pub fn to_owned(self) -> ConstantData {
use ConstantData::*;
match self {
BorrowedConstant::Integer { value } => Integer {
value: value.clone(),
},
BorrowedConstant::Float { value } => Float { value },
BorrowedConstant::Complex { value } => Complex { value },
BorrowedConstant::Boolean { value } => Boolean { value },
BorrowedConstant::Str { value } => Str {
value: value.to_owned(),
},
BorrowedConstant::Bytes { value } => Bytes {
value: value.to_owned(),
},
BorrowedConstant::Code { code } => Code {
code: Box::new(code.map_clone_bag(&BasicBag)),
},
BorrowedConstant::Tuple { elements } => Tuple {
elements: elements
.iter()
.map(|c| c.borrow_constant().to_owned())
.collect(),
},
BorrowedConstant::None => None,
BorrowedConstant::Ellipsis => Ellipsis,
}
}
}
/// 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,
}
impl OpArgType for ComparisonOperator {
fn to_oparg(self) -> u32 {
self as u32
}
fn from_oparg(x: u32) -> Option<Self> {
Some(match x {
0b001 => Self::Less,
0b010 => Self::Greater,
0b011 => Self::NotEqual,
0b100 => Self::Equal,
0b101 => Self::LessOrEqual,
0b110 => Self::GreaterOrEqual,
_ => return None,
})
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum TestOperator {
In,
NotIn,
Is,
IsNot,
/// two exceptions that match?
ExceptionMatch,
}
impl OpArgType for TestOperator {
fn to_oparg(self) -> u32 {
self as u32
}
fn from_oparg(x: u32) -> Option<Self> {
Some(match x {
0 => Self::In,
1 => Self::NotIn,
2 => Self::Is,
3 => Self::IsNot,
4 => Self::ExceptionMatch,
_ => return None,
})
}
}
/// 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,
Multiply,
MatrixMultiply,
Divide,
FloorDivide,
Modulo,
Add,
Subtract,
Lshift,
Rshift,
And,
Xor,
Or,
}
impl OpArgType for BinaryOperator {
fn to_oparg(self) -> u32 {
self as u32
}
fn from_oparg(x: u32) -> Option<Self> {
Some(match x {
0 => Self::Power,
1 => Self::Multiply,
2 => Self::MatrixMultiply,
3 => Self::Divide,
4 => Self::FloorDivide,
5 => Self::Modulo,
6 => Self::Add,
7 => Self::Subtract,
8 => Self::Lshift,
9 => Self::Rshift,
10 => Self::And,
11 => Self::Xor,
12 => Self::Or,
_ => return None,
})
}
}
/// The possible unary operators
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum UnaryOperator {
Not,
Invert,
Minus,
Plus,
}
impl OpArgType for UnaryOperator {
fn to_oparg(self) -> u32 {
self as u32
}
fn from_oparg(x: u32) -> Option<Self> {
Some(match x {
0 => Self::Not,
1 => Self::Invert,
2 => Self::Minus,
3 => Self::Plus,
_ => return None,
})
}
}
#[derive(Copy, Clone)]
pub struct UnpackExArgs {
pub before: u8,
pub after: u8,
}
impl OpArgType for UnpackExArgs {
#[inline(always)]
fn from_oparg(x: u32) -> Option<Self> {
let [before, after, ..] = x.to_le_bytes();
Some(Self { before, after })
}
#[inline(always)]
fn to_oparg(self) -> u32 {
u32::from_le_bytes([self.before, self.after, 0, 0])
}
}
impl fmt::Display for UnpackExArgs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "before: {}, after: {}", self.before, self.after)
}
}
/*
Maintain a stack of blocks on the VM.
pub enum BlockType {
Loop,
Except,
}
*/
/// Argument structure
pub struct Arguments<'a, N: AsRef<str>> {
pub posonlyargs: &'a [N],
pub args: &'a [N],
pub vararg: Option<&'a N>,
pub kwonlyargs: &'a [N],
pub varkwarg: Option<&'a N>,
}
impl<N: AsRef<str>> fmt::Debug for Arguments<'_, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
macro_rules! fmt_slice {
($x:expr) => {
format_args!("[{}]", $x.iter().map(AsRef::as_ref).format(", "))
};
}
f.debug_struct("Arguments")
.field("posonlyargs", &fmt_slice!(self.posonlyargs))
.field("args", &fmt_slice!(self.posonlyargs))
.field("vararg", &self.vararg.map(N::as_ref))
.field("kwonlyargs", &fmt_slice!(self.kwonlyargs))
.field("varkwarg", &self.varkwarg.map(N::as_ref))
.finish()
}
}
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 as usize;
let nkwargs = self.kwonlyarg_count as usize;
let mut varargspos = nargs + nkwargs;
let posonlyargs = &self.varnames[..self.posonlyarg_count as usize];
let args = &self.varnames[..nargs];
let kwonlyargs = &self.varnames[nargs..varargspos];
let vararg = if self.flags.contains(CodeFlags::HAS_VARARGS) {
let vararg = &self.varnames[varargspos];
varargspos += 1;
Some(vararg)
} else {
None
};
let varkwarg = if self.flags.contains(CodeFlags::HAS_VARKEYWORDS) {
Some(&self.varnames[varargspos])
} else {
None
};
Arguments {
posonlyargs,
args,
vararg,
kwonlyargs,
varkwarg,
}
}
/// Return the labels targeted by the instructions of this CodeObject
pub fn label_targets(&self) -> BTreeSet<Label> {
let mut label_targets = BTreeSet::new();
let mut arg_state = OpArgState::default();
for instruction in &*self.instructions {
let (instruction, arg) = arg_state.get(*instruction);
if let Some(l) = instruction.label_arg() {
label_targets.insert(l.get(arg));
}
}
label_targets
}
fn display_inner(
&self,
f: &mut fmt::Formatter,
expand_codeobjects: bool,
level: usize,
) -> fmt::Result {
let label_targets = self.label_targets();
let line_digits = (3).max(self.locations.last().unwrap().row.to_string().len());
let offset_digits = (4).max(self.instructions.len().to_string().len());
let mut last_line = u32::MAX;
let mut arg_state = OpArgState::default();
for (offset, &instruction) in self.instructions.iter().enumerate() {
let (instruction, arg) = arg_state.get(instruction);
// optional line number
let line = self.locations[offset].row;
if line != last_line {
if last_line != u32::MAX {
writeln!(f)?;
}
last_line = line;
write!(f, "{line:line_digits$}")?;
} else {
for _ in 0..line_digits {
write!(f, " ")?;
}
}
write!(f, " ")?;
// level indent
for _ in 0..level {
write!(f, " ")?;
}
// arrow and offset
let arrow = if label_targets.contains(&Label(offset as u32)) {
">>"
} else {
" "
};
write!(f, "{arrow} {offset:offset_digits$} ")?;
// instruction
instruction.fmt_dis(arg, f, self, expand_codeobjects, 21, level)?;
writeln!(f)?;
}
Ok(())
}
/// Recursively display this CodeObject
pub fn display_expand_codeobjects(&self) -> impl fmt::Display + '_ {
struct Display<'a, C: Constant>(&'a CodeObject<C>);
impl<C: Constant> fmt::Display for Display<'_, C> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.display_inner(f, true, 1)
}
}
Display(self)
}
/// Map this CodeObject to one that holds a Bag::Constant
pub fn map_bag<Bag: ConstantBag>(self, bag: Bag) -> CodeObject<Bag::Constant> {
let map_names = |names: Box<[C::Name]>| {
names
.into_vec()
.into_iter()
.map(|x| bag.make_name(x.as_ref()))
.collect::<Box<[_]>>()
};
CodeObject {
constants: self
.constants
.into_vec()
.into_iter()
.map(|x| bag.make_constant(x.borrow_constant()))
.collect(),
names: map_names(self.names),
varnames: map_names(self.varnames),
cellvars: map_names(self.cellvars),
freevars: map_names(self.freevars),
source_path: bag.make_name(self.source_path.as_ref()),
obj_name: bag.make_name(self.obj_name.as_ref()),
instructions: self.instructions,
locations: self.locations,
flags: self.flags,
posonlyarg_count: self.posonlyarg_count,
arg_count: self.arg_count,
kwonlyarg_count: self.kwonlyarg_count,
first_line_number: self.first_line_number,
max_stackdepth: self.max_stackdepth,
cell2arg: self.cell2arg,
}
}
/// Same as `map_bag` but clones `self`
pub fn map_clone_bag<Bag: ConstantBag>(&self, bag: &Bag) -> CodeObject<Bag::Constant> {
let map_names =
|names: &[C::Name]| names.iter().map(|x| bag.make_name(x.as_ref())).collect();
CodeObject {
constants: self
.constants
.iter()
.map(|x| bag.make_constant(x.borrow_constant()))
.collect(),
names: map_names(&self.names),
varnames: map_names(&self.varnames),
cellvars: map_names(&self.cellvars),
freevars: map_names(&self.freevars),
source_path: bag.make_name(self.source_path.as_ref()),
obj_name: bag.make_name(self.obj_name.as_ref()),
instructions: self.instructions.clone(),
locations: self.locations.clone(),
flags: self.flags,
posonlyarg_count: self.posonlyarg_count,
arg_count: self.arg_count,
kwonlyarg_count: self.kwonlyarg_count,
first_line_number: self.first_line_number,
max_stackdepth: self.max_stackdepth,
cell2arg: self.cell2arg.clone(),
}
}
}
impl CodeObject<ConstantData> {
/// Load a code object from bytes
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 => {
MarshalError::Eof
}
_ => MarshalError::InvalidBytecode,
})?;
marshal::deserialize_code(&mut &raw_bincode[..], BasicBag)
}
/// Serialize this bytecode to bytes.
pub fn to_bytes(&self) -> Vec<u8> {
let mut data = Vec::new();
marshal::serialize_code(&mut data, self);
lz4_flex::compress_prepend_size(&data)
}
}
impl<C: Constant> fmt::Display for CodeObject<C> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.display_inner(f, false, 1)?;
for constant in &*self.constants {
if let BorrowedConstant::Code { code } = constant.borrow_constant() {
writeln!(f, "\nDisassembly of {code:?}")?;
code.fmt(f)?;
}
}
Ok(())
}
}
impl Instruction {
/// Gets the label stored inside this instruction, if it exists
#[inline]
pub fn label_arg(&self) -> Option<Arg<Label>> {
match self {
Jump { target: l }
| JumpIfTrue { target: l }
| JumpIfFalse { target: l }
| JumpIfTrueOrPop { target: l }
| JumpIfFalseOrPop { target: l }
| ForIter { target: l }
| SetupFinally { handler: l }
| SetupExcept { handler: l }
| SetupWith { end: l }
| SetupAsyncWith { end: l }
| Break { target: l }
| Continue { target: l } => Some(*l),
_ => None,
}
}
/// Whether this is an unconditional branching
///
/// # Examples
///
/// ```
/// use rustpython_compiler_core::{Arg, Instruction};
/// let jump_inst = Instruction::Jump { target: Arg::marker() };
/// assert!(jump_inst.unconditional_branch())
/// ```
pub fn unconditional_branch(&self) -> bool {
matches!(
self,
Jump { .. } | Continue { .. } | Break { .. } | ReturnValue | Raise { .. }
)
}
/// What effect this instruction has on the stack
///
/// # Examples
///
/// ```
/// use rustpython_compiler_core::{Arg, Instruction, Label, UnaryOperator};
/// let (target, jump_arg) = Arg::new(Label(0xF));
/// let jump_instruction = Instruction::Jump { target };
/// let (op, invert_arg) = Arg::new(UnaryOperator::Invert);
/// let invert_instruction = Instruction::UnaryOperation { op };
/// assert_eq!(jump_instruction.stack_effect(jump_arg, true), 0);
/// assert_eq!(invert_instruction.stack_effect(invert_arg, false), 0);
/// ```
///
pub fn stack_effect(&self, arg: OpArg, jump: bool) -> i32 {
match self {
ImportName { .. } | ImportNameless => -1,
ImportStar => -1,
ImportFrom { .. } => 1,
LoadFast(_) | LoadNameAny(_) | LoadGlobal(_) | LoadDeref(_) | LoadClassDeref(_) => 1,
StoreFast(_) | StoreLocal(_) | StoreGlobal(_) | StoreDeref(_) => -1,
DeleteFast(_) | DeleteLocal(_) | DeleteGlobal(_) | DeleteDeref(_) => 0,
LoadClosure(_) => 1,
Subscript => -1,
StoreSubscript => -3,
DeleteSubscript => -2,
LoadAttr { .. } => 0,
StoreAttr { .. } => -2,
DeleteAttr { .. } => -1,
LoadConst { .. } => 1,
UnaryOperation { .. } => 0,
BinaryOperation { .. }
| BinaryOperationInplace { .. }
| TestOperation { .. }
| CompareOperation { .. } => -1,
Pop => -1,
Rotate2 | Rotate3 => 0,
Duplicate => 1,
Duplicate2 => 2,
GetIter => 0,
Continue { .. } => 0,
Break { .. } => 0,
Jump { .. } => 0,
JumpIfTrue { .. } | JumpIfFalse { .. } => -1,
JumpIfTrueOrPop { .. } | JumpIfFalseOrPop { .. } => {
if jump {
0
} else {
-1
}
}
MakeFunction(flags) => {
let flags = flags.get(arg);
-2 - flags.contains(MakeFunctionFlags::CLOSURE) as i32
- flags.contains(MakeFunctionFlags::ANNOTATIONS) as i32
- flags.contains(MakeFunctionFlags::KW_ONLY_DEFAULTS) as i32
- flags.contains(MakeFunctionFlags::DEFAULTS) as i32
+ 1
}
CallFunctionPositional { nargs } => -(nargs.get(arg) as i32) - 1 + 1,
CallMethodPositional { nargs } => -(nargs.get(arg) as i32) - 3 + 1,
CallFunctionKeyword { nargs } => -1 - (nargs.get(arg) as i32) - 1 + 1,
CallMethodKeyword { nargs } => -1 - (nargs.get(arg) as i32) - 3 + 1,
CallFunctionEx { has_kwargs } => -1 - (has_kwargs.get(arg) as i32) - 1 + 1,
CallMethodEx { has_kwargs } => -1 - (has_kwargs.get(arg) as i32) - 3 + 1,
LoadMethod { .. } => -1 + 3,
ForIter { .. } => {
if jump {
-1
} else {
1
}
}
ReturnValue => -1,
YieldValue => 0,
YieldFrom => -1,
SetupAnnotation | SetupLoop | SetupFinally { .. } | EnterFinally | EndFinally => 0,
SetupExcept { .. } => jump as i32,
SetupWith { .. } => (!jump) as i32,
WithCleanupStart => 0,
WithCleanupFinish => -1,
PopBlock => 0,
Raise { kind } => -(kind.get(arg) as u8 as i32),
BuildString { size }
| BuildTuple { size, .. }
| BuildTupleUnpack { size, .. }
| BuildList { size, .. }
| BuildListUnpack { size, .. }
| BuildSet { size, .. }
| BuildSetUnpack { size, .. } => -(size.get(arg) as i32) + 1,
BuildMap { size } => {
let nargs = size.get(arg) * 2;
-(nargs as i32) + 1
}
BuildMapForCall { size } => {
let nargs = size.get(arg);
-(nargs as i32) + 1
}
DictUpdate => -1,
BuildSlice { step } => -2 - (step.get(arg) as i32) + 1,
ListAppend { .. } | SetAdd { .. } => -1,
MapAdd { .. } => -2,
PrintExpr => -1,
LoadBuildClass => 1,
UnpackSequence { size } => -1 + size.get(arg) as i32,
UnpackEx { args } => {
let UnpackExArgs { before, after } = args.get(arg);
-1 + before as i32 + 1 + after as i32
}
FormatValue { .. } => -1,
PopException => 0,
Reverse { .. } => 0,
GetAwaitable => 0,
BeforeAsyncWith => 1,
SetupAsyncWith { .. } => {
if jump {
-1
} else {
0
}
}
GetAIter => 0,
GetANext => 1,
EndAsyncFor => -2,
ExtendedArg => 0,
}
}
pub fn display<'a>(
&'a self,
arg: OpArg,
ctx: &'a impl InstrDisplayContext,
) -> impl fmt::Display + 'a {
struct FmtFn<F>(F);
impl<F: Fn(&mut fmt::Formatter) -> fmt::Result> fmt::Display for FmtFn<F> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
(self.0)(f)
}
}
FmtFn(move |f: &mut fmt::Formatter| self.fmt_dis(arg, f, ctx, false, 0, 0))
}
#[allow(clippy::too_many_arguments)]
fn fmt_dis(
&self,
arg: OpArg,
f: &mut fmt::Formatter,
ctx: &impl InstrDisplayContext,
expand_codeobjects: bool,
pad: usize,
level: usize,
) -> fmt::Result {
macro_rules! w {
($variant:ident) => {
write!(f, stringify!($variant))
};
($variant:ident, $map:ident = $argmarker:expr) => {{
let arg = $argmarker.get(arg);
write!(f, "{:pad$}({}, {})", stringify!($variant), arg, $map(arg))
}};
($variant:ident, $argmarker:expr) => {
write!(f, "{:pad$}({})", stringify!($variant), $argmarker.get(arg))
};
($variant:ident, ?$argmarker:expr) => {
write!(
f,
"{:pad$}({:?})",
stringify!($variant),
$argmarker.get(arg)
)
};
}
let varname = |i: u32| ctx.get_varname(i as usize);
let name = |i: u32| ctx.get_name(i as usize);
let cellname = |i: u32| ctx.get_cellname(i as usize);
match self {
ImportName { idx } => w!(ImportName, name = idx),
ImportNameless => w!(ImportNameless),
ImportStar => w!(ImportStar),
ImportFrom { idx } => w!(ImportFrom, name = idx),
LoadFast(idx) => w!(LoadFast, varname = idx),
LoadNameAny(idx) => w!(LoadNameAny, name = idx),
LoadGlobal(idx) => w!(LoadGlobal, name = idx),
LoadDeref(idx) => w!(LoadDeref, cellname = idx),
LoadClassDeref(idx) => w!(LoadClassDeref, cellname = idx),
StoreFast(idx) => w!(StoreFast, varname = idx),
StoreLocal(idx) => w!(StoreLocal, name = idx),
StoreGlobal(idx) => w!(StoreGlobal, name = idx),
StoreDeref(idx) => w!(StoreDeref, cellname = idx),
DeleteFast(idx) => w!(DeleteFast, varname = idx),
DeleteLocal(idx) => w!(DeleteLocal, name = idx),
DeleteGlobal(idx) => w!(DeleteGlobal, name = idx),
DeleteDeref(idx) => w!(DeleteDeref, cellname = idx),
LoadClosure(i) => w!(LoadClosure, cellname = i),
Subscript => w!(Subscript),
StoreSubscript => w!(StoreSubscript),
DeleteSubscript => w!(DeleteSubscript),
StoreAttr { idx } => w!(StoreAttr, name = idx),
DeleteAttr { idx } => w!(DeleteAttr, name = idx),
LoadConst { idx } => {
let value = ctx.get_constant(idx.get(arg) as usize);
match value.borrow_constant() {
BorrowedConstant::Code { code } if expand_codeobjects => {
write!(f, "{:pad$}({:?}):", "LoadConst", code)?;
code.display_inner(f, true, level + 1)?;
Ok(())
}
c => {
write!(f, "{:pad$}(", "LoadConst")?;
c.fmt_display(f)?;
write!(f, ")")
}
}
}
UnaryOperation { op } => w!(UnaryOperation, ?op),
BinaryOperation { op } => w!(BinaryOperation, ?op),
BinaryOperationInplace { op } => w!(BinaryOperationInplace, ?op),
LoadAttr { idx } => w!(LoadAttr, name = idx),
TestOperation { op } => w!(TestOperation, ?op),
CompareOperation { op } => w!(CompareOperation, ?op),
Pop => w!(Pop),
Rotate2 => w!(Rotate2),
Rotate3 => w!(Rotate3),
Duplicate => w!(Duplicate),
Duplicate2 => w!(Duplicate2),
GetIter => w!(GetIter),
Continue { target } => w!(Continue, target),
Break { target } => w!(Break, target),
Jump { target } => w!(Jump, target),
JumpIfTrue { target } => w!(JumpIfTrue, target),
JumpIfFalse { target } => w!(JumpIfFalse, target),
JumpIfTrueOrPop { target } => w!(JumpIfTrueOrPop, target),
JumpIfFalseOrPop { target } => w!(JumpIfFalseOrPop, target),
MakeFunction(flags) => w!(MakeFunction, ?flags),
CallFunctionPositional { nargs } => w!(CallFunctionPositional, nargs),
CallFunctionKeyword { nargs } => w!(CallFunctionKeyword, nargs),
CallFunctionEx { has_kwargs } => w!(CallFunctionEx, has_kwargs),
LoadMethod { idx } => w!(LoadMethod, name = idx),
CallMethodPositional { nargs } => w!(CallMethodPositional, nargs),
CallMethodKeyword { nargs } => w!(CallMethodKeyword, nargs),
CallMethodEx { has_kwargs } => w!(CallMethodEx, has_kwargs),
ForIter { target } => w!(ForIter, target),
ReturnValue => w!(ReturnValue),
YieldValue => w!(YieldValue),
YieldFrom => w!(YieldFrom),
SetupAnnotation => w!(SetupAnnotation),
SetupLoop => w!(SetupLoop),
SetupExcept { handler } => w!(SetupExcept, handler),
SetupFinally { handler } => w!(SetupFinally, handler),
EnterFinally => w!(EnterFinally),
EndFinally => w!(EndFinally),
SetupWith { end } => w!(SetupWith, end),
WithCleanupStart => w!(WithCleanupStart),
WithCleanupFinish => w!(WithCleanupFinish),
BeforeAsyncWith => w!(BeforeAsyncWith),
SetupAsyncWith { end } => w!(SetupAsyncWith, end),
PopBlock => w!(PopBlock),
Raise { kind } => w!(Raise, ?kind),
BuildString { size } => w!(BuildString, size),
BuildTuple { size } => w!(BuildTuple, size),
BuildTupleUnpack { size } => w!(BuildTupleUnpack, size),
BuildList { size } => w!(BuildList, size),
BuildListUnpack { size } => w!(BuildListUnpack, size),
BuildSet { size } => w!(BuildSet, size),
BuildSetUnpack { size } => w!(BuildSetUnpack, size),
BuildMap { size } => w!(BuildMap, size),
BuildMapForCall { size } => w!(BuildMap, size),
DictUpdate => w!(DictUpdate),
BuildSlice { step } => w!(BuildSlice, step),
ListAppend { i } => w!(ListAppend, i),
SetAdd { i } => w!(SetAdd, i),
MapAdd { i } => w!(MapAdd, i),
PrintExpr => w!(PrintExpr),
LoadBuildClass => w!(LoadBuildClass),
UnpackSequence { size } => w!(UnpackSequence, size),
UnpackEx { args } => w!(UnpackEx, args),
FormatValue { conversion } => w!(FormatValue, ?conversion),
PopException => w!(PopException),
Reverse { amount } => w!(Reverse, amount),
GetAwaitable => w!(GetAwaitable),
GetAIter => w!(GetAIter),
GetANext => w!(GetANext),
EndAsyncFor => w!(EndAsyncFor),
ExtendedArg => w!(ExtendedArg, Arg::<u32>::marker()),
}
}
}
pub trait InstrDisplayContext {
type Constant: Constant;
fn get_constant(&self, i: usize) -> &Self::Constant;
fn get_name(&self, i: usize) -> &str;
fn get_varname(&self, i: usize) -> &str;
fn get_cellname(&self, i: usize) -> &str;
}
impl<C: Constant> InstrDisplayContext for CodeObject<C> {
type Constant = C;
fn get_constant(&self, i: usize) -> &C {
&self.constants[i]
}
fn get_name(&self, i: usize) -> &str {
self.names[i].as_ref()
}
fn get_varname(&self, i: usize) -> &str {
self.varnames[i].as_ref()
}
fn get_cellname(&self, i: usize) -> &str {
self.cellvars
.get(i)
.unwrap_or_else(|| &self.freevars[i - self.cellvars.len()])
.as_ref()
}
}
impl fmt::Display for ConstantData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.borrow_constant().fmt_display(f)
}
}
impl<C: Constant> fmt::Debug for CodeObject<C> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"<code object {} at ??? file {:?}, line {}>",
self.obj_name.as_ref(),
self.source_path.as_ref(),
self.first_line_number
)
}
}
/// A frozen module. Holds a code object and whether it is part of a package
#[derive(Debug)]
pub struct FrozenModule {
pub code: CodeObject<ConstantData>,
pub package: bool,
}
pub mod frozen_lib {
use super::*;
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 mut data = marshal::Cursor { data, position: 0 };
let remaining = data.read_u32().unwrap();
FrozenModulesIter { remaining, data }
}
pub struct FrozenModulesIter {
remaining: u32,
data: marshal::Cursor<Vec<u8>>,
}
impl Iterator for FrozenModulesIter {
type Item = (String, FrozenModule);
fn next(&mut self) -> Option<Self::Item> {
if self.remaining > 0 {
let entry = read_entry(&mut self.data).unwrap();
self.remaining -= 1;
Some(entry)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(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
I: IntoIterator<Item = (&'a str, &'a FrozenModule)>,
I::IntoIter: ExactSizeIterator + Clone,
{
let iter = lib.into_iter();
let mut data = Vec::new();
write_lib(&mut data, iter);
lz4_flex::compress_prepend_size(&data)
}
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);
}
}
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);
}
}