use core::ops::ControlFlow;
use rustpython_compiler_core::bytecode::{
CodeObject, ConstantData, Constants, Instruction, OpArg, OpArgState,
};
use rustpython_jit::{CompiledCode, JitType};
use rustpython_wtf8::{Wtf8, Wtf8Buf};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Function {
code: Box<CodeObject>,
annotations: HashMap<Wtf8Buf, StackValue>,
}
impl Function {
pub fn compile(self) -> CompiledCode {
let mut arg_types = Vec::new();
for arg in self.code.arg_names().args {
let arg_type = match self.annotations.get(AsRef::<Wtf8>::as_ref(arg.as_str())) {
Some(StackValue::String(annotation)) => match annotation.as_str() {
"int" => JitType::Int,
"float" => JitType::Float,
"bool" => JitType::Bool,
_ => panic!("Unrecognised jit type"),
},
_ => panic!("Argument have annotation"),
};
arg_types.push(arg_type);
}
let ret_type = match self.annotations.get(AsRef::<Wtf8>::as_ref("return")) {
Some(StackValue::String(annotation)) => match annotation.as_str() {
"int" => Some(JitType::Int),
"float" => Some(JitType::Float),
"bool" => Some(JitType::Bool),
_ => panic!("Unrecognised jit type"),
},
_ => None,
};
rustpython_jit::compile(&self.code, &arg_types, ret_type).expect("Compile failure")
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
enum StackValue {
String(String),
None,
Map(HashMap<Wtf8Buf, StackValue>),
Code(Box<CodeObject>),
Function(Function),
Slice(Box<[StackValue; 3]>),
Frozenset(Vec<StackValue>),
}
impl From<ConstantData> for StackValue {
fn from(value: ConstantData) -> Self {
match value {
ConstantData::Str { value } => {
StackValue::String(value.into_string().expect("surrogate in test code"))
}
ConstantData::None => StackValue::None,
ConstantData::Code { code } => StackValue::Code(code),
ConstantData::Slice { elements } => {
let [start, stop, step] = *elements;
StackValue::Slice(Box::new([start.into(), stop.into(), step.into()]))
}
ConstantData::Frozenset { elements } => {
StackValue::Frozenset(elements.into_iter().map(Into::into).collect())
}
c => unimplemented!("constant {:?} isn't yet supported in py_function!", c),
}
}
}
fn extract_annotations_from_annotate_code(code: &CodeObject) -> HashMap<Wtf8Buf, StackValue> {
let mut annotations = HashMap::new();
let mut stack: Vec<(bool, usize)> = Vec::new(); let mut op_arg_state = OpArgState::default();
for &word in code.instructions.iter() {
let (instruction, arg) = op_arg_state.get(word);
match instruction {
Instruction::LoadConst { consti } => {
stack.push((true, consti.get(arg).as_usize()));
}
Instruction::LoadName { namei } => {
stack.push((false, namei.get(arg) as usize));
}
Instruction::LoadGlobal { namei } => {
stack.push((false, (namei.get(arg) >> 1) as usize));
}
Instruction::BuildMap { count } => {
let count = count.get(arg) as usize;
let start = stack.len().saturating_sub(count * 2);
let pairs: Vec<_> = stack.drain(start..).collect();
for chunk in pairs.chunks(2) {
if chunk.len() == 2 {
let (key_is_const, key_idx) = chunk[0];
let (val_is_const, val_idx) = chunk[1];
if key_is_const
&& let ConstantData::Str { value } =
&code.constants[(key_idx as u32).into()]
{
let param_name = value;
let type_name = if val_is_const {
match code.constants.get(val_idx) {
Some(ConstantData::Str { value }) => value
.as_str()
.map(|s| s.to_owned())
.unwrap_or_else(|_| value.to_string_lossy().into_owned()),
Some(other) => panic!(
"Unsupported annotation const for '{:?}' at idx {}: {:?}",
param_name, val_idx, other
),
None => panic!(
"Annotation const idx out of bounds for '{:?}': {} (len={})",
param_name,
val_idx,
code.constants.len()
),
}
} else {
match code.names.get(val_idx) {
Some(name) => name.clone(),
None => panic!(
"Annotation name idx out of bounds for '{:?}': {} (len={})",
param_name,
val_idx,
code.names.len()
),
}
};
annotations.insert(param_name.clone(), StackValue::String(type_name));
}
}
}
return annotations;
}
Instruction::Resume { .. }
| Instruction::LoadFast { .. }
| Instruction::CompareOp { .. }
| Instruction::ExtendedArg
| Instruction::Cache
| Instruction::NotTaken => {
}
Instruction::ReturnValue => {
return annotations;
}
_ => {
stack.clear();
}
}
}
annotations
}
pub struct StackMachine {
stack: Vec<StackValue>,
locals: HashMap<String, StackValue>,
}
impl StackMachine {
pub fn new() -> StackMachine {
StackMachine {
stack: Vec::new(),
locals: HashMap::new(),
}
}
pub fn run(&mut self, code: CodeObject) {
let mut op_arg_state = OpArgState::default();
let _ = code.instructions.iter().try_for_each(|&word| {
let (instruction, arg) = op_arg_state.get(word);
self.process_instruction(instruction, arg, &code.constants, &code.names)
});
}
fn process_instruction(
&mut self,
instruction: Instruction,
arg: OpArg,
constants: &Constants<ConstantData>,
names: &[String],
) -> ControlFlow<()> {
match instruction {
Instruction::Resume { .. } | Instruction::Cache | Instruction::NotTaken => {
}
Instruction::LoadConst { consti } => {
self.stack.push(constants[consti.get(arg)].clone().into())
}
Instruction::LoadName { namei } => self
.stack
.push(StackValue::String(names[namei.get(arg) as usize].clone())),
Instruction::StoreName { namei } => {
let idx = namei.get(arg);
self.locals
.insert(names[idx as usize].clone(), self.stack.pop().unwrap());
}
Instruction::StoreAttr { .. } => {
self.stack.pop().unwrap();
self.stack.pop().unwrap();
}
Instruction::BuildMap { count } => {
let mut map = HashMap::new();
for _ in 0..count.get(arg) {
let value = self.stack.pop().unwrap();
let name = if let Some(StackValue::String(name)) = self.stack.pop() {
Wtf8Buf::from(name)
} else {
unimplemented!("no string keys isn't yet supported in py_function!")
};
map.insert(name, value);
}
self.stack.push(StackValue::Map(map));
}
Instruction::MakeFunction => {
let code = if let Some(StackValue::Code(code)) = self.stack.pop() {
code
} else {
panic!("Expected function code")
};
self.stack.push(StackValue::Function(Function {
code,
annotations: HashMap::new(), }));
}
Instruction::SetFunctionAttribute { flag } => {
let func = if let Some(StackValue::Function(func)) = self.stack.pop() {
func
} else {
panic!("Expected function on stack for SET_FUNCTION_ATTRIBUTE")
};
let attr_value = self.stack.pop().expect("Expected attribute value on stack");
let flag_value = flag.get(arg);
match flag_value {
rustpython_compiler_core::bytecode::MakeFunctionFlag::Annotate => {
if let StackValue::Function(annotate_func) = attr_value {
let annotate_code = &annotate_func.code;
let annotations = extract_annotations_from_annotate_code(annotate_code);
let updated_func = Function {
code: func.code,
annotations,
};
self.stack.push(StackValue::Function(updated_func));
} else {
panic!("Expected annotate function for ANNOTATE flag");
}
}
rustpython_compiler_core::bytecode::MakeFunctionFlag::Annotations => {
if let StackValue::Map(annotations) = attr_value {
let updated_func = Function {
code: func.code,
annotations,
};
self.stack.push(StackValue::Function(updated_func));
} else {
panic!("Expected annotations to be a map");
}
}
_ => {
self.stack.push(StackValue::Function(func));
}
}
}
Instruction::ReturnValue => return ControlFlow::Break(()),
Instruction::ExtendedArg => {}
_ => unimplemented!(
"instruction {:?} isn't yet supported in py_function!",
instruction
),
}
ControlFlow::Continue(())
}
pub fn get_function(&self, name: &str) -> Function {
if let Some(StackValue::Function(function)) = self.locals.get(name) {
function.clone()
} else {
panic!("There was no function named {name}")
}
}
}
macro_rules! jit_function {
($func_name:ident => $($t:tt)*) => {
{
let code = rustpython_derive::py_compile!(
crate_name = "rustpython_compiler_core",
source = $($t)*
);
let code = code.decode(rustpython_compiler_core::bytecode::BasicBag);
let mut machine = $crate::common::StackMachine::new();
machine.run(code);
machine.get_function(stringify!($func_name)).compile()
}
};
($func_name:ident($($arg_name:ident:$arg_type:ty),*) -> $ret_type:ty => $($t:tt)*) => {
{
let jit_code = jit_function!($func_name => $($t)*);
move |$($arg_name:$arg_type),*| -> Result<$ret_type, rustpython_jit::JitArgumentError> {
jit_code
.invoke(&[$($arg_name.into()),*])
.map(|ret| match ret {
Some(ret) => ret.try_into().expect("jit function returned unexpected type"),
None => panic!("jit function unexpectedly returned None")
})
}
}
};
($func_name:ident($($arg_name:ident:$arg_type:ty),*) => $($t:tt)*) => {
{
let jit_code = jit_function!($func_name => $($t)*);
move |$($arg_name:$arg_type),*| -> Result<(), rustpython_jit::JitArgumentError> {
jit_code
.invoke(&[$($arg_name.into()),*])
.map(|ret| match ret {
Some(ret) => panic!("jit function unexpectedly returned a value {:?}", ret),
None => ()
})
}
}
};
}