add more crates

Signed-off-by: xermicus <cyrill@parity.io>
This commit is contained in:
xermicus
2023-12-09 17:48:52 +01:00
parent c04ae9a5c3
commit 7a094f17c0
22 changed files with 191 additions and 34 deletions
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "revive-ir"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
evmil = { workspace = true }
petgraph = { workspace = true }
primitive-types = { workspace = true }
indexmap = { workspace = true }
+58
View File
@@ -0,0 +1,58 @@
use primitive_types::U256;
#[derive(Clone, Copy)]
pub enum Kind {
Constant(U256),
Temporary(usize),
Stack,
}
#[derive(Clone, Copy)]
pub struct Address {
pub kind: Kind,
pub type_hint: Option<Type>,
}
impl From<(Kind, Option<Type>)> for Address {
fn from(value: (Kind, Option<Type>)) -> Self {
Self {
kind: value.0,
type_hint: value.1,
}
}
}
impl Address {
pub fn new(kind: Kind, type_hint: Option<Type>) -> Self {
Self { kind, type_hint }
}
}
#[derive(Clone, Copy)]
pub enum Type {
Int { size: u8 },
Bytes { size: u8 },
Bool,
}
impl Type {
pub fn int(size: u8) -> Self {
Self::Int { size }
}
fn bytes(size: u8) -> Self {
Self::Bytes { size }
}
}
impl Default for Type {
fn default() -> Self {
Type::Int { size: 32 }
}
}
pub enum LinearMemory {
CallData,
Memory,
ReturnData,
}
+224
View File
@@ -0,0 +1,224 @@
use std::fmt::Write;
use std::ops::Range;
use evmil::bytecode;
use petgraph::{
dot::{Config, Dot},
graph::DiGraph,
stable_graph::NodeIndex,
};
use crate::{
instruction::{self, Instruction},
symbol::SymbolTable,
};
#[derive(Clone, Debug)]
pub struct EvmInstruction {
pub bytecode_offset: usize,
pub instruction: bytecode::Instruction,
}
#[derive(Debug, Default)]
pub struct BasicBlock {
pub entry: Option<Entry>,
pub opcodes: Range<usize>,
pub instructions: Vec<Instruction>,
}
#[derive(Clone, Copy, Default)]
pub enum BasicBlockFormatOption {
ByteCode,
Ir,
#[default]
None,
}
impl BasicBlock {
fn format(&self, evm_bytecode: &[EvmInstruction], options: BasicBlockFormatOption) -> String {
let offset = evm_bytecode[self.opcodes.start].bytecode_offset;
let start = if let Some(Entry::Start) = self.entry {
"Start\n".to_string()
} else {
String::new()
};
let instructions = match options {
BasicBlockFormatOption::ByteCode => evm_bytecode[self.opcodes.start..self.opcodes.end]
.iter()
.fold(String::new(), |mut acc, opcode| {
writeln!(&mut acc, "{:?}", opcode.instruction).unwrap();
acc
}),
BasicBlockFormatOption::Ir => {
self.instructions
.iter()
.fold(String::new(), |mut acc, instruction| {
writeln!(&mut acc, "{instruction}").unwrap();
acc
})
}
_ => String::new(),
};
format!("{start}Offset: 0x{offset:02x}\n---\n{instructions}")
}
}
#[derive(Clone, Debug)]
pub enum Entry {
Start,
Jumpdest(NodeIndex),
Else(NodeIndex),
}
#[derive(Debug)]
pub enum Jump {
Direct,
Indirect,
}
pub struct Program {
pub evm_instructions: Vec<EvmInstruction>,
pub cfg: DiGraph<BasicBlock, Jump>,
pub symbol_table: SymbolTable,
}
impl Program {
pub fn new(bytecode: Vec<bytecode::Instruction>) -> Self {
let mut cfg = DiGraph::new();
let mut symbol_table = SymbolTable::default();
let mut evm_instructions = Vec::with_capacity(bytecode.len());
let mut current_block = Some(BasicBlock {
entry: Some(Entry::Start),
..Default::default()
});
let mut bytecode_offset = 0;
for (index, opcode) in bytecode.iter().enumerate() {
evm_instructions.push(EvmInstruction {
bytecode_offset,
instruction: opcode.clone(),
});
bytecode_offset += opcode.length();
let instructions = instruction::translate(opcode, &mut symbol_table);
use bytecode::Instruction::*;
match opcode {
JUMPDEST => {
// If we are already in a bb, conclude it
let entry = current_block.take().map(|mut node| {
node.opcodes.end = index + 1;
let entry = node.entry.clone();
let node_index = cfg.add_node(node);
// If the block had an entry, add an edge from the previous block to it
if let Some(Entry::Else(incoming)) | Some(Entry::Jumpdest(incoming)) = entry
{
cfg.add_edge(incoming, node_index, Jump::Direct);
}
node_index
});
// JUMPDEST implicitly starts a new bb
current_block = Some(BasicBlock {
entry: entry.map(Entry::Jumpdest),
opcodes: Range {
start: index + 1,
end: index + 1,
},
..Default::default()
});
}
JUMP | STOP | RETURN | REVERT | INVALID => {
// Conclude this bb; if we are not already in a bb we must create a new one
let mut node = current_block.take().unwrap_or_else(|| BasicBlock {
opcodes: Range {
start: index,
end: index + 1,
},
..Default::default()
});
node.instructions.extend(instructions);
node.opcodes.end = index + 1;
let entry = node.entry.clone();
let node_index = cfg.add_node(node);
// If the block had an entry, add an edge from the previous block to it
if let Some(Entry::Else(incoming)) | Some(Entry::Jumpdest(incoming)) = entry {
cfg.add_edge(incoming, node_index, Jump::Direct);
}
}
JUMPI => {
// Conclude this bb; if we are not already in a bb we must create a new one
let mut node = current_block.take().unwrap_or_else(|| BasicBlock {
opcodes: Range {
start: index,
end: index + 1,
},
..Default::default()
});
node.instructions.extend(instructions);
node.opcodes.end = index + 1;
let entry = node.entry.clone();
let node_index = cfg.add_node(node);
// If the block had an entry, add an edge from the previous block to it
if let Some(Entry::Else(incoming)) | Some(Entry::Jumpdest(incoming)) = entry {
cfg.add_edge(incoming, node_index, Jump::Direct);
}
// JUMPI implicitly starts a new bb for the else branch
current_block = Some(BasicBlock {
entry: Some(Entry::Else(node_index)),
opcodes: Range {
start: index + 1,
end: index + 1,
},
..Default::default()
});
}
_ => current_block
.get_or_insert(BasicBlock {
opcodes: Range {
start: index,
end: index + 1,
},
..Default::default()
})
.instructions
.extend(instructions),
}
}
Self {
evm_instructions,
cfg,
symbol_table,
}
}
pub fn dot(&self, format_options: BasicBlockFormatOption) {
let get_node_attrs = move |_, (_, node): (_, &BasicBlock)| {
format!(
"label = \"{}\"",
node.format(&self.evm_instructions, format_options)
)
};
let dot = Dot::with_attr_getters(
&self.cfg,
&[Config::EdgeNoLabel, Config::NodeNoLabel],
&|_, edge| format!("label = \"{:?}\"", edge.weight()),
&get_node_attrs,
);
println!("{dot:?}");
}
}
+410
View File
@@ -0,0 +1,410 @@
use evmil::bytecode::Instruction as EvmInstruction;
use primitive_types::U256;
use std::fmt::Write;
use crate::{
symbol::{Global, Symbol, SymbolTable, Type},
POINTER_SIZE,
};
#[derive(PartialEq, Debug)]
pub enum Instruction {
/// `x = y op z`
BinaryAssign {
x: Symbol,
y: Symbol,
operator: Operator,
z: Symbol,
},
/// `x = op y`
UnaryAssign {
x: Symbol,
operator: Operator,
y: Symbol,
},
/// `branch target`
UncoditionalBranch { target: Symbol },
/// `branch target if condition`
ConditionalBranch { condition: Symbol, target: Symbol },
/// `call(label, n)`
Procedure {
symbol: Global,
parameters: Vec<Symbol>,
},
/// `x = call(label, n)`
Function {
symbol: Global,
x: Symbol,
parameters: Vec<Symbol>,
},
/// `x = y`
Copy { x: Symbol, y: Symbol },
/// `x[index] = y`
IndexedAssign { x: Symbol, index: Symbol, y: Symbol },
/// `x = y[index]`
IndexedCopy { x: Symbol, y: Symbol, index: Symbol },
}
impl Instruction {
fn target_address(&self) -> Symbol {
match self {
Instruction::Copy { x, .. } => *x,
Instruction::IndexedAssign { x, .. } => *x,
Instruction::IndexedCopy { x, .. } => *x,
_ => unreachable!(),
}
}
}
impl std::fmt::Display for Instruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BinaryAssign { x, y, operator, z } => write!(f, "{x} = {y} {operator:?} {z}"),
Self::UnaryAssign { x, operator, y } => write!(f, "{x} = {operator:?} {y} "),
Self::UncoditionalBranch { target } => write!(f, "branch {target}"),
Self::ConditionalBranch { condition, target } => {
write!(f, "if {condition} branch {target}")
}
Self::Procedure { symbol, parameters } => write!(
f,
"{symbol:?}({})",
parameters.iter().fold(String::new(), |mut acc, p| {
write!(&mut acc, "{p}, ").unwrap();
acc
})
),
Self::Function {
symbol,
x,
parameters: args,
} => write!(
f,
"{x} = {symbol:?}({})",
args.iter().fold(String::new(), |mut acc, p| {
write!(&mut acc, "{p}, ").unwrap();
acc
})
),
Self::Copy { x, y } => write!(f, "{x} = {y}"),
Self::IndexedAssign { x, index, y } => write!(f, "{x}[{index}] = {y}"),
Self::IndexedCopy { x, y, index } => write!(f, "{x} = {y}[{index}]"),
}
}
}
#[derive(PartialEq, Debug)]
pub enum Operator {
Add,
Mul,
Sub,
Div,
SDiv,
Mod,
SMod,
AddMod,
MulMod,
Exp,
SignExtend,
LessThat,
GreaterThan,
SignedLessThan,
SignedGreaterThan,
Eq,
IsZero,
And,
Or,
Xor,
Not,
Byte,
ShiftLeft,
ShiftRight,
ShiftArithmeticRight,
}
struct StackPop {
decrement: Instruction,
load: Instruction,
}
/// Pop a value from the stack.
///
/// Returns 2 `Instruction`: Decrementing the stack pointer and the value copy.
fn stack_pop(symbol_table: &mut SymbolTable) -> StackPop {
let decrement = decrement_stack_height(symbol_table);
let load = Instruction::IndexedCopy {
x: symbol_table.temporary(None),
y: symbol_table.global(Global::Stack),
index: symbol_table.global(Global::StackHeight),
};
StackPop { decrement, load }
}
/// Decrease the stack height by one.
fn decrement_stack_height(symbol_table: &mut SymbolTable) -> Instruction {
Instruction::BinaryAssign {
x: symbol_table.global(Global::StackHeight),
y: symbol_table.global(Global::StackHeight),
operator: Operator::Sub,
z: symbol_table.constant(U256::one(), Some(Global::StackHeight.typ())),
}
}
struct StackPush {
assign: Instruction,
increment: Instruction,
}
/// Push a `value` to the stack.
///
/// Returns 2 `Instruction`: the value assign and the stack height increase.
fn stack_push(symbol_table: &mut SymbolTable, value: Symbol) -> StackPush {
let assign = Instruction::IndexedAssign {
x: symbol_table.global(Global::Stack),
index: symbol_table.global(Global::StackHeight),
y: value,
};
let increment = increment_stack_height(symbol_table);
StackPush { assign, increment }
}
/// Increment the stack height by one.
fn increment_stack_height(symbol_table: &mut SymbolTable) -> Instruction {
Instruction::BinaryAssign {
x: symbol_table.global(Global::StackHeight),
y: symbol_table.global(Global::StackHeight),
operator: Operator::Add,
z: symbol_table.constant(U256::one(), Some(Global::StackHeight.typ())),
}
}
/// Lower an EVM instruction into corresponding 3AC instructions.
pub fn translate(opcode: &EvmInstruction, symbol_table: &mut SymbolTable) -> Vec<Instruction> {
use EvmInstruction::*;
match opcode {
JUMPDEST => Vec::new(),
PUSH(bytes) => {
let type_hint = Some(Type::Bytes(bytes.len()));
let value = symbol_table.constant(U256::from_big_endian(bytes), type_hint);
let push = stack_push(symbol_table, value);
vec![push.assign, push.increment]
}
POP => vec![decrement_stack_height(symbol_table)],
MSTORE => {
let offset = stack_pop(symbol_table);
let value = stack_pop(symbol_table);
let store = Instruction::IndexedAssign {
x: symbol_table.global(Global::Memory),
index: offset.load.target_address(),
y: value.load.target_address(),
};
vec![
offset.decrement,
offset.load,
value.decrement,
value.load,
store,
]
}
JUMP => {
let target = stack_pop(symbol_table);
let jump = Instruction::UncoditionalBranch {
target: target.load.target_address(),
};
vec![target.decrement, target.load, jump]
}
RETURN => {
let offset = stack_pop(symbol_table);
let size = stack_pop(symbol_table);
let procedure = Instruction::Procedure {
symbol: Global::Return,
parameters: vec![offset.load.target_address(), size.load.target_address()],
};
vec![
offset.decrement,
offset.load,
size.decrement,
size.load,
procedure,
]
}
CALLDATACOPY => {
let destination_offset = stack_pop(symbol_table);
let offset = stack_pop(symbol_table);
let size = stack_pop(symbol_table);
let parameters = vec![
destination_offset.load.target_address(),
offset.load.target_address(),
size.load.target_address(),
];
let procedure = Instruction::Procedure {
symbol: Global::MemoryCopy,
parameters,
};
vec![
destination_offset.decrement,
destination_offset.load,
offset.decrement,
offset.load,
size.decrement,
size.load,
procedure,
]
}
CALLDATALOAD => {
let index = stack_pop(symbol_table);
let value = Instruction::IndexedCopy {
x: symbol_table.temporary(None),
y: symbol_table.global(Global::CallData),
index: index.load.target_address(),
};
let push = stack_push(symbol_table, value.target_address());
vec![
index.decrement,
index.load,
value,
push.assign,
push.increment,
]
}
STOP => {
vec![Instruction::Procedure {
symbol: Global::Stop,
parameters: Default::default(),
}]
}
INVALID => {
let offset = symbol_table.constant(U256::zero(), Some(Type::Int(POINTER_SIZE)));
let size = symbol_table.constant(U256::zero(), Some(Type::Int(POINTER_SIZE)));
vec![Instruction::Procedure {
symbol: Global::Revert,
parameters: vec![offset, size],
}]
}
REVERT => {
let offset = stack_pop(symbol_table);
let size = stack_pop(symbol_table);
let procedure = Instruction::Procedure {
symbol: Global::Revert,
parameters: vec![offset.load.target_address(), size.load.target_address()],
};
vec![
offset.decrement,
offset.load,
size.decrement,
size.load,
procedure,
]
}
//_ => todo!("{opcode}"),
_ => Vec::new(),
}
}
#[cfg(test)]
mod tests {
use evmil::bytecode;
use primitive_types::U256;
use crate::{
instruction::Operator,
symbol::{Address, Global, Kind, Symbol, Type},
};
use super::Instruction;
#[test]
fn lower_push_works() {
let mut symbol_table = Default::default();
let opcode = bytecode::Instruction::PUSH(vec![0x01]);
let result = super::translate(&opcode, &mut symbol_table);
let expected = vec![
Instruction::IndexedAssign {
x: Symbol {
address: Address::Label(Global::Stack),
type_hint: Global::Stack.typ(),
kind: Global::Stack.kind(),
},
index: Symbol {
address: Address::Label(Global::StackHeight),
type_hint: Global::StackHeight.typ(),
kind: Global::StackHeight.kind(),
},
y: Symbol {
address: Address::Constant(U256::one()),
type_hint: Type::Bytes(1),
kind: Kind::Value,
},
},
Instruction::BinaryAssign {
x: Symbol {
address: Address::Label(Global::StackHeight),
type_hint: Global::StackHeight.typ(),
kind: Global::StackHeight.kind(),
},
y: Symbol {
address: Address::Label(Global::StackHeight),
type_hint: Global::StackHeight.typ(),
kind: Global::StackHeight.kind(),
},
operator: Operator::Add,
z: Symbol {
address: Address::Constant(U256::one()),
type_hint: Global::StackHeight.typ(),
kind: Kind::Value,
},
},
];
assert_eq!(expected, result);
}
}
+12
View File
@@ -0,0 +1,12 @@
pub mod cfg;
pub mod instruction;
pub mod symbol;
pub static POINTER_SIZE: usize = 32;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {}
}
+196
View File
@@ -0,0 +1,196 @@
use indexmap::IndexSet;
use primitive_types::U256;
use crate::POINTER_SIZE;
#[derive(Debug, Default)]
pub struct SymbolTable {
symbols: IndexSet<Symbol>,
nonce: usize,
}
impl SymbolTable {
fn next(&mut self) -> usize {
let current = self.nonce;
self.nonce += 1;
current
}
pub fn temporary(&mut self, type_hint: Option<Type>) -> Symbol {
let id = self.next();
let symbol = Symbol {
address: Address::Temporary(id),
type_hint: type_hint.unwrap_or_default(),
kind: Kind::Value,
};
assert!(self.symbols.insert(symbol));
symbol
}
pub fn constant(&mut self, value: U256, type_hint: Option<Type>) -> Symbol {
let symbol = Symbol {
address: Address::Constant(value),
type_hint: type_hint.unwrap_or_default(),
kind: Kind::Value,
};
self.symbols.insert(symbol);
symbol
}
pub fn global(&mut self, label: Global) -> Symbol {
let symbol = Symbol {
address: Address::Label(label),
type_hint: label.typ(),
kind: label.kind(),
};
self.symbols.insert(symbol);
symbol
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct Symbol {
pub address: Address,
pub type_hint: Type,
pub kind: Kind,
}
impl std::fmt::Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({} {})", self.type_hint, self.address)?;
match self.kind {
Kind::Pointer => write!(f, "*"),
_ => Ok(()),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Address {
Constant(U256),
Temporary(usize),
Label(Global),
}
impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Constant(value) => write!(f, "0x{value:02x}"),
Self::Temporary(n) => write!(f, "tmp_{n}"),
Self::Label(label) => write!(f, "{label:?}"),
}
}
}
impl Address {
pub fn from_be_bytes(bytes: &[u8]) -> Self {
Self::Constant(U256::from_big_endian(bytes))
}
}
#[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Copy)]
pub enum Type {
#[default]
Word,
UInt(usize),
Int(usize),
Bytes(usize),
Bool,
}
impl std::fmt::Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Word => write!(f, "word"),
Self::UInt(size) => write!(f, "u{}", size),
Self::Int(size) => write!(f, "i{}", size),
Self::Bytes(size) => write!(f, "bytes{size}"),
Self::Bool => write!(f, "bool"),
}
}
}
impl Type {
pub fn pointer() -> Self {
Self::UInt(POINTER_SIZE)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Kind {
Pointer,
Value,
Function,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Global {
Stack,
StackHeight,
CallData,
Memory,
ReturnData,
MemoryCopy,
// EVM runtime environment
Sha3,
Address,
CallDataLoad,
CallDataSize,
CallDataCopy,
CodeSize,
CodeCopy,
GasPrice,
ExtCodeSize,
ExtCodeCopy,
ReturnDataSize,
ReturnDataCopy,
ExtCodeHash,
BlockHash,
Coinbase,
Timestamp,
BlockNumber,
PrevRanDao,
GasLimit,
ChainId,
SelfBalance,
BaseFee,
SLoad,
SStore,
Gas,
Create,
Create2,
Call,
StaticCall,
DelegateCall,
CallCode,
Return,
Stop,
Revert,
SelfDestruct,
Event,
}
impl Global {
pub fn typ(&self) -> Type {
match self {
Self::Stack | Self::CallData | Self::Memory | Self::ReturnData => Type::pointer(),
Self::StackHeight => Type::UInt(POINTER_SIZE),
_ => Type::Word,
}
}
pub fn kind(&self) -> Kind {
match self {
Self::Stack | Self::CallData | Self::Memory | Self::ReturnData => Kind::Pointer,
Self::StackHeight => Kind::Value,
_ => Kind::Function,
}
}
}