-
Notifications
You must be signed in to change notification settings - Fork 0
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
Changes from 21 commits
d8cff13
e77291e
dc75558
74fbec5
10c3d71
2032447
6ad191f
51aa444
8540a03
ee9024a
b95e9ad
ab646f7
2d72b49
28c3de6
1372af5
d0e306a
6ad1ae9
af229ef
49a0e65
8be07ee
febca33
a95325e
c700a44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
|
@@ -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; | ||
|
@@ -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); | ||
|
@@ -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, | ||
|
@@ -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(), | ||
|
@@ -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, | ||
} | ||
} | ||
|
||
|
@@ -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( | ||
|
@@ -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() | ||
|
||
|
@@ -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>, | ||
input_abi: Option<Functions>, | ||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> { | ||
assert_ne!(self.evm_start_function, 0); | ||
|
@@ -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); | ||
|
@@ -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); | ||
|
@@ -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()) | ||
|
@@ -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 | ||
|
||
|
@@ -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) | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
} | ||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And here |
||
} | ||
None // not found | ||
} |
There was a problem hiding this comment.
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