Skip to content

Commit

Permalink
replace parity_wasm (#23)
Browse files Browse the repository at this point in the history
obsolete parity_wasm lib replaced with wasm-encoder + wasmparser
  • Loading branch information
shishkin-pavel authored Apr 19, 2023
1 parent 6e97f83 commit b1c8e70
Show file tree
Hide file tree
Showing 5 changed files with 853 additions and 109 deletions.
4 changes: 3 additions & 1 deletion bin/evm2near/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ clap = { version = "3.2.17", features = ["color", "derive", "unicode"] }
ethnum = "1.2.2"
evm_rs = "0.3.2"
hex = "0.4.3"
parity-wasm = "0.45.0"
serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.85"
sha3 = "0.10"
wild = "2.1.0"
wasmparser = "0.102.0"
wasm-encoder = "0.25.0"
anyhow = "1.0"

[[bin]]
name = "evm2near"
Expand Down
185 changes: 87 additions & 98 deletions bin/evm2near/src/compile.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// This is free and unencumbered software released into the public domain.

use std::{
borrow::Cow,
collections::{HashMap, HashSet},
convert::TryInto,
fmt::Display,
Expand All @@ -9,21 +10,16 @@ use std::{
};

use evm_rs::{parse_opcode, Opcode, Program};
use parity_wasm::{
builder::{FunctionBuilder, ModuleBuilder, SignatureBuilder},
elements::{
BlockType, BrTableData, ExportEntry, FuncBody, ImportCountType, Instruction, Instructions,
Internal, Local, Module, TableType, ValueType,
},
};
use relooper::graph::{enrichments::EnrichedCfg, relooper::ReBlock, supergraph::reduce};
use relooper::graph::{relooper::ReSeq, supergraph::SLabel};
use wasm_encoder::{BlockType, ExportKind, Function, Instruction, Module, ValType};

use crate::{
abi::Functions,
analyze::{basic_cfg, BasicCfg, CfgNode, Idx, Offs},
config::CompilerConfig,
encode::encode_push,
wasm_translate::{translator::DataMode, Export, ModuleBuilder, Signature},
};

const TABLE_OFFSET: i32 = 0x1000;
Expand Down Expand Up @@ -64,10 +60,10 @@ fn evm_idx_to_offs(program: &Program) -> HashMap<Idx, Offs> {
idx2offs
}

pub fn compile(
input_program: &Program,
pub fn compile<'a>(
input_program: &'a Program,
input_abi: Option<Functions>,
runtime_library: Module,
runtime_library: ModuleBuilder<'a>,
config: CompilerConfig,
) -> Module {
let mut compiler = Compiler::new(runtime_library, config);
Expand All @@ -77,36 +73,32 @@ pub fn compile(
compiler.emit_abi_execute();
let abi_data = compiler.emit_abi_methods(input_abi).unwrap();

let mut output_module = compiler.builder.build();

let tables = output_module.table_section_mut().unwrap().entries_mut();
tables[0] = TableType::new(0xFFFF, Some(0xFFFF)); // grow the table to 65,535 elements

// Overwrite the `_abi_buffer` data segment in evmlib with the ABI data
// (function parameter names and types) for all public Solidity contract
// methods:
let abi_buffer_ptr: usize = compiler.abi_buffer_off.try_into().unwrap();
for data in output_module.data_section_mut().unwrap().entries_mut() {
let min_ptr: usize = match data.offset().as_ref().unwrap().code() {
[Instruction::I32Const(off), Instruction::End] => (*off).try_into().unwrap(),
_ => continue, // skip any nonstandard data segments
for data in compiler.builder.data.iter_mut() {
let min_ptr: usize = match data.mode {
DataMode::Active {
memory_index: _,
offset_instr: Instruction::I32Const(off),
} => off.try_into().unwrap(),
_ => continue,
};
let max_ptr: usize = min_ptr + data.value().len();
let max_ptr: usize = min_ptr + data.data.len();

if abi_buffer_ptr >= min_ptr && abi_buffer_ptr < max_ptr {
let min_off = abi_buffer_ptr - min_ptr;
let max_off = min_off + abi_data.len();
assert!(min_ptr + max_off <= max_ptr);
data.value_mut()[min_off..max_off].copy_from_slice(&abi_data);
data.data[min_off..max_off].copy_from_slice(&abi_data);
break; // found it
}
}
output_module
compiler.builder.build()
}

type DataOffset = i32;
type FunctionIndex = u32;

struct Compiler {
struct Compiler<'a> {
config: CompilerConfig,
abi_buffer_off: DataOffset,
abi_buffer_len: usize,
Expand All @@ -119,13 +111,12 @@ struct Compiler {
evm_pop_function: FunctionIndex, // _evm_pop_u32
evm_burn_gas: FunctionIndex, // _evm_burn_gas
evm_pc_function: FunctionIndex, // _evm_set_pc
function_import_count: usize,
builder: ModuleBuilder,
builder: ModuleBuilder<'a>,
}

impl Compiler {
impl<'a> Compiler<'a> {
/// Instantiates a new compiler state.
fn new(runtime_library: Module, config: CompilerConfig) -> Compiler {
fn new(runtime_library: ModuleBuilder, config: CompilerConfig) -> Compiler {
Compiler {
config,
abi_buffer_off: find_abi_buffer(&runtime_library).unwrap(),
Expand All @@ -140,8 +131,7 @@ impl Compiler {
evm_pop_function: find_runtime_function(&runtime_library, "_evm_pop_u32").unwrap(),
evm_burn_gas: find_runtime_function(&runtime_library, "_evm_burn_gas").unwrap(),
evm_pc_function: find_runtime_function(&runtime_library, "_evm_set_pc").unwrap(),
function_import_count: runtime_library.import_count(ImportCountType::Function),
builder: parity_wasm::builder::from_module(runtime_library),
builder: runtime_library,
}
}

Expand All @@ -155,13 +145,13 @@ impl Compiler {
}

/// Emit an empty `_start` function to make all WebAssembly runtimes happy.
fn emit_wasm_start(self: &mut Compiler) {
fn emit_wasm_start(&mut self) {
_ = self.emit_function(Some("_start".to_string()), vec![]);
}

/// Synthesizes a start function that initializes the EVM state with the
/// correct configuration.
fn emit_evm_start(self: &mut Compiler) {
fn emit_evm_start(&mut self) {
assert_ne!(self.evm_init_function, 0);

self.evm_start_function = self.emit_function(
Expand All @@ -175,7 +165,7 @@ impl Compiler {
);
}

fn emit_abi_execute(self: &mut Compiler) {
fn emit_abi_execute(&mut self) {
assert_ne!(self.evm_start_function, 0);
assert_ne!(self.evm_exec_function, 0); // filled in during compile_cfg()

Expand All @@ -195,7 +185,7 @@ impl Compiler {
/// contract's ABI, enabling users to directly call a contract method
/// without going through the low-level `execute` EVM dispatcher.
pub fn emit_abi_methods(
self: &mut Compiler,
&mut self,
input_abi: Option<Functions>,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
assert_ne!(self.evm_start_function, 0);
Expand Down Expand Up @@ -263,26 +253,26 @@ impl Compiler {
//TODO self is only used for `evm_pop_function`
fn unfold_cfg(
&self,
program: &Program,
program: &'a Program,
cfg_part: &ReSeq<SLabel<CfgNode<EvmBlock>>>,
res: &mut Vec<Instruction>,
res: &mut Vec<Instruction<'a>>,
wasm_idx2evm_idx: &mut HashMap<Idx, Idx>,
) {
for block in cfg_part.0.iter() {
match block {
ReBlock::Block(inner_seq) => {
res.push(Instruction::Block(BlockType::NoResult));
res.push(Instruction::Block(BlockType::Empty));
self.unfold_cfg(program, inner_seq, res, wasm_idx2evm_idx);
res.push(Instruction::End);
}
ReBlock::Loop(inner_seq) => {
res.push(Instruction::Loop(BlockType::NoResult));
res.push(Instruction::Loop(BlockType::Empty));
self.unfold_cfg(program, inner_seq, res, wasm_idx2evm_idx);
res.push(Instruction::End);
}
ReBlock::If(true_branch, false_branch) => {
res.push(Instruction::Call(self.evm_pop_function));
res.push(Instruction::If(BlockType::NoResult));
res.push(Instruction::If(BlockType::Empty));
self.unfold_cfg(program, true_branch, res, wasm_idx2evm_idx);
res.push(Instruction::Else);
self.unfold_cfg(program, false_branch, res, wasm_idx2evm_idx);
Expand Down Expand Up @@ -366,12 +356,10 @@ impl Compiler {
linear_table[cond] = br_num + 1; // increment due to additional block wrapping (for unreachable instruction)
}

res.push(Instruction::Block(BlockType::NoResult));
res.push(Instruction::Block(BlockType::Empty));
res.push(Instruction::Call(self.evm_pop_function));
let br_table = Instruction::BrTable(Box::new(BrTableData {
table: linear_table.into_boxed_slice(),
default: 0,
}));
let cow = Cow::Owned(linear_table);
let br_table = Instruction::BrTable(cow, 0);
res.push(br_table);
res.push(Instruction::End);
res.push(Instruction::Unreachable);
Expand Down Expand Up @@ -442,7 +430,7 @@ impl Compiler {
let wasm_nodes: Vec<_> = wasm
.iter()
.enumerate()
.map(|(idx, w_op)| format!("wasm_{}[label=\"{}\"];", idx, w_op))
.map(|(idx, w_op)| format!("wasm_{}[label=\"{:?}\"];", idx, w_op))
.collect();

let wasm_seq_links: Vec<_> = (0..wasm.len())
Expand Down Expand Up @@ -490,7 +478,7 @@ subgraph cluster_wasm {{ label = \"wasm\"
}

/// Compiles the program's control-flow graph.
fn compile_cfg(self: &mut Compiler, program: &Program) {
fn compile_cfg(&mut self, program: &'a Program) {
assert_ne!(self.evm_start_function, 0); // filled in during emit_start()
assert_eq!(self.evm_exec_function, 0); // filled in below

Expand Down Expand Up @@ -545,7 +533,7 @@ subgraph cluster_wasm {{ label = \"wasm\"
}

/// Compiles the invocation of an EVM operator (operands must be already pushed).
fn compile_operator(&self, op: &Opcode) -> Instruction {
fn compile_operator(&self, op: &Opcode) -> Instruction<'a> {
let op = op.zeroed();
let op_idx = self.op_table.get(&op).unwrap();
Instruction::Call(*op_idx)
Expand All @@ -557,85 +545,86 @@ subgraph cluster_wasm {{ label = \"wasm\"
Some(_) | None => code.push(Instruction::End),
};

let func_sig = SignatureBuilder::new()
.with_params(vec![])
.with_results(vec![])
.build_sig();

let func_locals = vec![Local::new(1, ValueType::I32)]; // needed for dynamic branches
let func_body = FuncBody::new(func_locals, Instructions::new(code));

let func = FunctionBuilder::new()
.with_signature(func_sig)
.with_body(func_body)
.build();
let func_sig = Signature {
params: vec![],
results: vec![],
};

let func_loc = self.builder.push_function(func);
let mut func_body = Function::new_with_locals_types(vec![ValType::I32]);
for instr in code {
func_body.instruction(&instr);
}

let func_idx = func_loc.signature + self.function_import_count as u32; // TODO: https://github.com/paritytech/parity-wasm/issues/304
let imports_len = u32::try_from(self.builder.imports.len()).unwrap();
let func_idx = self.builder.add_function(func_sig, func_body) + imports_len;

if let Some(name) = name {
let func_export = ExportEntry::new(name, Internal::Function(func_idx));

let _ = self.builder.push_export(func_export);
let func_export = Export {
name,
kind: ExportKind::Func,
index: func_idx,
};
let _ = self.builder.add_export(func_export);
}

func_idx
}
}

fn make_op_table(module: &Module) -> HashMap<Opcode, FunctionIndex> {
fn make_op_table(module: &ModuleBuilder) -> HashMap<Opcode, FunctionIndex> {
let mut result: HashMap<Opcode, FunctionIndex> = HashMap::new();
for export in module.export_section().unwrap().entries() {
match export.internal() {
&Internal::Function(op_idx) => match export.field() {
for export in module.exports.iter() {
if let Export {
name,
kind: ExportKind::Func,
index,
} = export
{
match name.as_str() {
"_abi_buffer" | "_evm_start" | "_evm_init" | "_evm_call" | "_evm_exec"
| "_evm_post_exec" | "_evm_pop_u32" | "_evm_push_u32" | "_evm_burn_gas"
| "_evm_set_pc" | "execute" => {}
export_sym => match parse_opcode(&export_sym.to_ascii_uppercase()) {
None => unreachable!(), // TODO
Some(op) => _ = result.insert(op, op_idx),
Some(op) => _ = result.insert(op, *index),
},
},
_ => continue,
}
}
}
result
}

fn find_runtime_function(module: &Module, name: &str) -> Option<FunctionIndex> {
for export in module.export_section().unwrap().entries() {
match export.internal() {
&Internal::Function(op_idx) => {
if export.field() == name {
return Some(op_idx);
}
fn find_runtime_function(module: &ModuleBuilder, func_name: &str) -> Option<FunctionIndex> {
for export in module.exports.iter() {
if let Export {
name,
kind: ExportKind::Func,
index,
} = export
{
if name == func_name {
return Some(*index);
}
_ => continue,
}
}
None // not found
}

fn find_abi_buffer(module: &Module) -> Option<DataOffset> {
for export in module.export_section().unwrap().entries() {
match export.internal() {
&Internal::Global(idx) => {
if export.field() == "_abi_buffer" {
// found it
let global = module
.global_section()
.unwrap()
.entries()
.get(idx as usize)
.unwrap();
match global.init_expr().code().first().unwrap() {
Instruction::I32Const(off) => return Some(*off),
_ => return None,
}
fn find_abi_buffer(module: &ModuleBuilder) -> Option<DataOffset> {
for export in module.exports.iter() {
if let Export {
name,
kind: ExportKind::Global,
index,
} = export
{
if name == "_abi_buffer" {
let g = module.globals.get(*index as usize).unwrap();
match g.init_instr {
wasm_encoder::Instruction::I32Const(off) => return Some(off),
_ => return None,
}
}
_ => continue,
}
}
None // not found
Expand Down
2 changes: 1 addition & 1 deletion bin/evm2near/src/encode.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// This is free and unencumbered software released into the public domain.

use evm_rs::Opcode;
use parity_wasm::elements::Instruction;
use wasm_encoder::Instruction;

pub fn encode_push(op: &Opcode) -> Vec<Instruction> {
let mut result = vec![];
Expand Down
Loading

0 comments on commit b1c8e70

Please sign in to comment.