Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

replace parity_wasm #23

Merged
merged 23 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
188 changes: 90 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(self: &mut Compiler<'a>) {
_ = 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(self: &mut Compiler<'a>) {
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(self: &mut Compiler<'a>) {
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,
self: &mut Compiler<'a>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this PR, but I think we could write this simply as &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(self: &mut Compiler<'a>, 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,86 +545,90 @@ 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,
}
}
continue;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This continue doesn't seem needed. It's at the bottom of the loop body, so the only thing left to do it continue to the next iteration.

}
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,
}
continue;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

}
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,
}
continue;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here

}
None // not found
}
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