mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-07-13 19:35:43 +00:00
@@ -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 }
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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:?}");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user