Gas for mem optimization (#1768)

* Optimizing mem_gas_cost calculations

* Memoizing current mem gas cost

* Optimizing jump destinations
This commit is contained in:
Tomasz Drwięga 2016-07-30 15:38:44 +02:00 committed by Gav Wood
parent 85c471b905
commit f56b89010d
11 changed files with 139 additions and 88 deletions

14
Cargo.lock generated
View File

@ -77,6 +77,19 @@ dependencies = [
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bit-set"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bit-vec"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "0.3.3"
@ -243,6 +256,7 @@ dependencies = [
name = "ethcore"
version = "1.3.0"
dependencies = [
"bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clippy 0.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -17,23 +17,25 @@ env_logger = "0.3"
rustc-serialize = "0.3"
heapsize = "0.3"
rust-crypto = "0.2.34"
time = "0.1"
ethcore-util = { path = "../util" }
evmjit = { path = "../evmjit", optional = true }
ethash = { path = "../ethash" }
num_cpus = "0.2"
clippy = { version = "0.0.79", optional = true}
crossbeam = "0.2.9"
lazy_static = "0.2"
bloomchain = "0.1"
rayon = "0.3.1"
semver = "0.2"
bit-set = "0.4"
time = "0.1"
evmjit = { path = "../evmjit", optional = true }
clippy = { version = "0.0.79", optional = true}
ethash = { path = "../ethash" }
ethcore-util = { path = "../util" }
ethcore-devtools = { path = "../devtools" }
ethjson = { path = "../json" }
bloomchain = "0.1"
ethcore-ipc = { path = "../ipc/rpc" }
rayon = "0.3.1"
ethstore = { path = "../ethstore" }
semver = "0.2"
ethcore-ipc-nano = { path = "../ipc/nano" }
[dependencies.hyper]
git = "https://github.com/ethcore/hyper"
default-features = false

View File

@ -107,9 +107,9 @@ pub trait CostType: ops::Mul<Output=Self> + ops::Div<Output=Self> + ops::Add<Out
fn overflow_add(self, other: Self) -> (Self, bool);
/// Multiple with overflow
fn overflow_mul(self, other: Self) -> (Self, bool);
/// Single-step full multiplication and division: `self*other/div`
/// Single-step full multiplication and shift: `(self*other) >> shr`
/// Should not overflow on intermediate steps
fn overflow_mul_div(self, other: Self, div: Self) -> (Self, bool);
fn overflow_mul_shr(self, other: Self, shr: usize) -> (Self, bool);
}
impl CostType for U256 {
@ -133,14 +133,14 @@ impl CostType for U256 {
Uint::overflowing_mul(self, other)
}
fn overflow_mul_div(self, other: Self, div: Self) -> (Self, bool) {
fn overflow_mul_shr(self, other: Self, shr: usize) -> (Self, bool) {
let x = self.full_mul(other);
let (U512(parts), o) = Uint::overflowing_div(x, U512::from(div));
let U512(parts) = x;
let overflow = (parts[4] | parts[5] | parts[6] | parts[7]) > 0;
let U512(parts) = x >> shr;
(
U256([parts[0], parts[1], parts[2], parts[3]]),
o | overflow
overflow
)
}
}
@ -169,11 +169,13 @@ impl CostType for usize {
self.overflowing_mul(other)
}
fn overflow_mul_div(self, other: Self, div: Self) -> (Self, bool) {
fn overflow_mul_shr(self, other: Self, shr: usize) -> (Self, bool) {
let (c, o) = U128::from(self).overflowing_mul(U128::from(other));
let (U128(parts), o1) = c.overflowing_div(U128::from(div));
let U128(parts) = c;
let overflow = o | (parts[1] > 0);
let U128(parts) = c >> shr;
let result = parts[0] as usize;
let overflow = o | o1 | (parts[1] > 0) | (parts[0] > result as u64);
let overflow = overflow | (parts[0] > result as u64);
(result, overflow)
}
}
@ -189,13 +191,13 @@ pub trait Evm {
#[test]
fn should_calculate_overflow_mul_div_without_overflow() {
fn should_calculate_overflow_mul_shr_without_overflow() {
// given
let num = 10_000_000;
let num = 1048576;
// when
let (res1, o1) = U256::from(num).overflow_mul_div(U256::from(num), U256::from(num));
let (res2, o2) = num.overflow_mul_div(num, num);
let (res1, o1) = U256::from(num).overflow_mul_shr(U256::from(num), 20);
let (res2, o2) = num.overflow_mul_shr(num, 20);
// then
assert_eq!(res1, U256::from(num));
@ -205,22 +207,21 @@ fn should_calculate_overflow_mul_div_without_overflow() {
}
#[test]
fn should_calculate_overflow_mul_div_with_overflow() {
fn should_calculate_overflow_mul_shr_with_overflow() {
// given
let max = ::std::u64::MAX;
let num1 = U256([max, max, max, max]);
let num2 = ::std::usize::MAX;
// when
let (res1, o1) = num1.overflow_mul_div(num1, num1 - U256::from(2));
let (res2, o2) = num2.overflow_mul_div(num2, num2 - 2);
let (res1, o1) = num1.overflow_mul_shr(num1, 256);
let (res2, o2) = num2.overflow_mul_shr(num2, 64);
// then
// (x+2)^2/x = (x^2 + 4x + 4)/x = x + 4 + 4/x ~ (MAX-2) + 4 + 0 = 1
assert_eq!(res2, 1);
assert_eq!(res2, num2 - 1);
assert!(o2);
assert_eq!(res1, U256::from(1));
assert_eq!(res1, !U256::zero() - U256::one());
assert!(o1);
}

View File

@ -18,8 +18,8 @@
use util::common::*;
use evm::{self, Schedule};
use types::executed::CallType;
use env_info::*;
use types::executed::CallType;
/// Result of externalities create function.
pub enum ContractCreateResult {

View File

@ -37,6 +37,7 @@ enum InstructionCost<Cost: CostType> {
pub struct Gasometer<Gas: CostType> {
pub current_gas: Gas,
pub current_mem_gas: Gas,
}
impl<Gas: CostType> Gasometer<Gas> {
@ -44,6 +45,7 @@ impl<Gas: CostType> Gasometer<Gas> {
pub fn new(current_gas: Gas) -> Self {
Gasometer {
current_gas: current_gas,
current_mem_gas: Gas::from(0),
}
}
@ -62,7 +64,7 @@ impl<Gas: CostType> Gasometer<Gas> {
info: &InstructionInfo,
stack: &Stack<U256>,
current_mem_size: usize,
) -> evm::Result<(Gas, usize)> {
) -> evm::Result<(Gas, Gas, usize)> {
let schedule = ext.schedule();
let tier = instructions::get_tier_idx(info.tier);
let default_gas = Gas::from(schedule.tier_step_gas[tier]);
@ -76,11 +78,11 @@ impl<Gas: CostType> Gasometer<Gas> {
let newval = stack.peek(1);
let val = U256::from(ext.storage_at(&address).as_slice());
let gas = if U256::zero() == val && &U256::zero() != newval {
let gas = if val.is_zero() && !newval.is_zero() {
schedule.sstore_set_gas
} else {
// Refund for below case is added when actually executing sstore
// !self.is_zero(&val) && self.is_zero(newval)
// !is_zero(&val) && is_zero(newval)
schedule.sstore_reset_gas
};
InstructionCost::Gas(Gas::from(gas))
@ -89,25 +91,25 @@ impl<Gas: CostType> Gasometer<Gas> {
InstructionCost::Gas(Gas::from(schedule.sload_gas))
},
instructions::MSTORE | instructions::MLOAD => {
InstructionCost::GasMem(default_gas, try!(self.mem_needed_const(stack.peek(0), 32)))
InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 32)))
},
instructions::MSTORE8 => {
InstructionCost::GasMem(default_gas, try!(self.mem_needed_const(stack.peek(0), 1)))
InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 1)))
},
instructions::RETURN => {
InstructionCost::GasMem(default_gas, try!(self.mem_needed(stack.peek(0), stack.peek(1))))
InstructionCost::GasMem(default_gas, try!(mem_needed(stack.peek(0), stack.peek(1))))
},
instructions::SHA3 => {
let w = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(1))), 31));
let words = w >> 5;
let gas = Gas::from(schedule.sha3_gas) + (Gas::from(schedule.sha3_word_gas) * words);
InstructionCost::GasMem(gas, try!(self.mem_needed(stack.peek(0), stack.peek(1))))
InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1))))
},
instructions::CALLDATACOPY | instructions::CODECOPY => {
InstructionCost::GasMemCopy(default_gas, try!(self.mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2))))
InstructionCost::GasMemCopy(default_gas, try!(mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2))))
},
instructions::EXTCODECOPY => {
InstructionCost::GasMemCopy(default_gas, try!(self.mem_needed(stack.peek(1), stack.peek(3))), try!(Gas::from_u256(*stack.peek(3))))
InstructionCost::GasMemCopy(default_gas, try!(mem_needed(stack.peek(1), stack.peek(3))), try!(Gas::from_u256(*stack.peek(3))))
},
instructions::LOG0...instructions::LOG4 => {
let no_of_topics = instructions::get_log_topics(instruction);
@ -115,13 +117,13 @@ impl<Gas: CostType> Gasometer<Gas> {
let data_gas = overflowing!(try!(Gas::from_u256(*stack.peek(1))).overflow_mul(Gas::from(schedule.log_data_gas)));
let gas = overflowing!(data_gas.overflow_add(Gas::from(log_gas)));
InstructionCost::GasMem(gas, try!(self.mem_needed(stack.peek(0), stack.peek(1))))
InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1))))
},
instructions::CALL | instructions::CALLCODE => {
let mut gas = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(0))), schedule.call_gas));
let mem = cmp::max(
try!(self.mem_needed(stack.peek(5), stack.peek(6))),
try!(self.mem_needed(stack.peek(3), stack.peek(4)))
try!(mem_needed(stack.peek(5), stack.peek(6))),
try!(mem_needed(stack.peek(3), stack.peek(4)))
);
let address = u256_to_address(stack.peek(1));
@ -130,7 +132,7 @@ impl<Gas: CostType> Gasometer<Gas> {
gas = overflowing!(gas.overflow_add(Gas::from(schedule.call_new_account_gas)));
};
if stack.peek(2) > &U256::zero() {
if !stack.peek(2).is_zero() {
gas = overflowing!(gas.overflow_add(Gas::from(schedule.call_value_transfer_gas)));
};
@ -139,14 +141,14 @@ impl<Gas: CostType> Gasometer<Gas> {
instructions::DELEGATECALL => {
let gas = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(0))), schedule.call_gas));
let mem = cmp::max(
try!(self.mem_needed(stack.peek(4), stack.peek(5))),
try!(self.mem_needed(stack.peek(2), stack.peek(3)))
try!(mem_needed(stack.peek(4), stack.peek(5))),
try!(mem_needed(stack.peek(2), stack.peek(3)))
);
InstructionCost::GasMem(gas, mem)
},
instructions::CREATE => {
let gas = Gas::from(schedule.create_gas);
let mem = try!(self.mem_needed(stack.peek(1), stack.peek(2)));
let mem = try!(mem_needed(stack.peek(1), stack.peek(2)));
InstructionCost::GasMem(gas, mem)
},
instructions::EXP => {
@ -160,66 +162,65 @@ impl<Gas: CostType> Gasometer<Gas> {
match cost {
InstructionCost::Gas(gas) => {
Ok((gas, 0))
Ok((gas, self.current_mem_gas, 0))
},
InstructionCost::GasMem(gas, mem_size) => {
let (mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size));
let gas = overflowing!(gas.overflow_add(mem_gas));
Ok((gas, new_mem_size))
let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size));
let gas = overflowing!(gas.overflow_add(mem_gas_cost));
Ok((gas, new_mem_gas, new_mem_size))
},
InstructionCost::GasMemCopy(gas, mem_size, copy) => {
let (mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size));
let copy = overflowing!(add_gas_usize(copy, 31));
let copy_gas = Gas::from(schedule.copy_gas) * (copy / Gas::from(32 as usize));
let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size));
let copy = overflowing!(add_gas_usize(copy, 31)) >> 5;
let copy_gas = Gas::from(schedule.copy_gas) * copy;
let gas = overflowing!(gas.overflow_add(copy_gas));
let gas = overflowing!(gas.overflow_add(mem_gas));
Ok((gas, new_mem_size))
let gas = overflowing!(gas.overflow_add(mem_gas_cost));
Ok((gas, new_mem_gas, new_mem_size))
}
}
}
fn is_zero(&self, val: &Gas) -> bool {
&Gas::from(0) == val
}
fn mem_needed_const(&self, mem: &U256, add: usize) -> evm::Result<Gas> {
Gas::from_u256(overflowing!(mem.overflowing_add(U256::from(add))))
}
fn mem_needed(&self, offset: &U256, size: &U256) -> evm::Result<Gas> {
if self.is_zero(&try!(Gas::from_u256(*size))) {
return Ok(Gas::from(0));
}
Gas::from_u256(overflowing!(offset.overflowing_add(*size)))
}
fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &Gas) -> evm::Result<(Gas, usize)> {
fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &Gas) -> evm::Result<(Gas, Gas, usize)> {
let gas_for_mem = |mem_size: Gas| {
let s = mem_size >> 5;
// s * memory_gas + s * s / quad_coeff_div
let a = overflowing!(s.overflow_mul(Gas::from(schedule.memory_gas)));
// Calculate s*s/quad_coeff_div
let b = overflowing!(s.overflow_mul_div(s, Gas::from(schedule.quad_coeff_div)));
debug_assert_eq!(schedule.quad_coeff_div, 512);
let b = overflowing!(s.overflow_mul_shr(s, 9));
Ok(overflowing!(a.overflow_add(b)))
};
let current_mem_size = Gas::from(current_mem_size);
let req_mem_size_rounded = (overflowing!(mem_size.overflow_add(Gas::from(31 as usize))) >> 5) << 5;
let mem_gas_cost = if req_mem_size_rounded > current_mem_size {
let (mem_gas_cost, new_mem_gas) = if req_mem_size_rounded > current_mem_size {
let new_mem_gas = try!(gas_for_mem(req_mem_size_rounded));
let current_mem_gas = try!(gas_for_mem(current_mem_size));
new_mem_gas - current_mem_gas
(new_mem_gas - self.current_mem_gas, new_mem_gas)
} else {
Gas::from(0)
(Gas::from(0), self.current_mem_gas)
};
Ok((mem_gas_cost, req_mem_size_rounded.as_usize()))
Ok((mem_gas_cost, new_mem_gas, req_mem_size_rounded.as_usize()))
}
}
#[inline]
fn mem_needed_const<Gas: CostType>(mem: &U256, add: usize) -> evm::Result<Gas> {
Gas::from_u256(overflowing!(mem.overflowing_add(U256::from(add))))
}
#[inline]
fn mem_needed<Gas: CostType>(offset: &U256, size: &U256) -> evm::Result<Gas> {
if size.is_zero() {
return Ok(Gas::from(0));
}
Gas::from_u256(overflowing!(offset.overflowing_add(*size)))
}
#[inline]
fn add_gas_usize<Gas: CostType>(value: Gas, num: usize) -> (Gas, bool) {
value.overflow_add(Gas::from(num))
@ -251,9 +252,10 @@ fn test_calculate_mem_cost() {
let mem_size = 5;
// when
let (mem_cost, mem_size) = gasometer.mem_gas_cost(&schedule, current_mem_size, &mem_size).unwrap();
let (mem_cost, new_mem_gas, mem_size) = gasometer.mem_gas_cost(&schedule, current_mem_size, &mem_size).unwrap();
// then
assert_eq!(mem_cost, 3);
assert_eq!(new_mem_gas, 3);
assert_eq!(mem_size, 32);
}

View File

@ -41,6 +41,7 @@ use common::*;
use types::executed::CallType;
use super::instructions::{self, Instruction, InstructionInfo};
use evm::{self, MessageCallResult, ContractCreateResult, GasLeft, CostType};
use bit_set::BitSet;
#[cfg(feature = "evm-debug")]
fn color(instruction: Instruction, name: &'static str) -> String {
@ -115,12 +116,13 @@ impl<Cost: CostType> evm::Evm for Interpreter<Cost> {
try!(self.verify_instruction(ext, instruction, &info, &stack));
// Calculate gas cost
let (gas_cost, mem_size) = try!(gasometer.get_gas_cost_mem(ext, instruction, &info, &stack, self.mem.size()));
let (gas_cost, mem_gas, mem_size) = try!(gasometer.get_gas_cost_mem(ext, instruction, &info, &stack, self.mem.size()));
// TODO: make compile-time removable if too much of a performance hit.
let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &gas_cost.as_u256());
try!(gasometer.verify_gas(&gas_cost));
self.mem.expand(mem_size);
gasometer.current_mem_gas = mem_gas;
gasometer.current_gas = gasometer.current_gas - gas_cost;
evm_debug!({
@ -540,10 +542,10 @@ impl<Cost: CostType> Interpreter<Cost> {
}
}
fn verify_jump(&self, jump_u: U256, valid_jump_destinations: &HashSet<usize>) -> evm::Result<usize> {
fn verify_jump(&self, jump_u: U256, valid_jump_destinations: &BitSet) -> evm::Result<usize> {
let jump = jump_u.low_u64() as usize;
if valid_jump_destinations.contains(&jump) && jump_u < U256::from(!0 as usize) {
if valid_jump_destinations.contains(jump) && U256::from(jump) == jump_u {
Ok(jump)
} else {
Err(evm::Error::BadJumpDestination {
@ -765,8 +767,8 @@ impl<Cost: CostType> Interpreter<Cost> {
Ok(())
}
fn find_jump_destinations(&self, code: &[u8]) -> HashSet<CodePosition> {
let mut jump_dests = HashSet::new();
fn find_jump_destinations(&self, code: &[u8]) -> BitSet {
let mut jump_dests = BitSet::with_capacity(code.len());
let mut position = 0;
while position < code.len() {
@ -818,5 +820,5 @@ fn test_find_jump_destinations() {
let valid_jump_destinations = interpreter.find_jump_destinations(&code);
// then
assert!(valid_jump_destinations.contains(&66));
assert!(valid_jump_destinations.contains(66));
}

View File

@ -35,3 +35,4 @@ pub use self::evm::{Evm, Error, Finalize, GasLeft, Result, CostType};
pub use self::ext::{Ext, ContractCreateResult, MessageCallResult};
pub use self::factory::{Factory, VMType};
pub use self::schedule::Schedule;
pub use types::executed::CallType;

View File

@ -129,3 +129,14 @@ impl Schedule {
}
}
}
#[test]
#[cfg(test)]
fn schedule_evm_assumptions() {
let s1 = Schedule::new_frontier();
let s2 = Schedule::new_homestead();
// To optimize division we assume 2**9 for quad_coeff_div
assert_eq!(s1.quad_coeff_div, 512);
assert_eq!(s2.quad_coeff_div, 512);
}

View File

@ -95,6 +95,7 @@ pub extern crate ethstore;
extern crate semver;
extern crate ethcore_ipc_nano as nanoipc;
extern crate ethcore_devtools as devtools;
extern crate bit_set;
#[cfg(feature = "jit" )] extern crate evmjit;

20
evmbin/Cargo.lock generated
View File

@ -47,6 +47,19 @@ dependencies = [
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bit-set"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bit-vec"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "0.3.3"
@ -168,6 +181,7 @@ dependencies = [
name = "ethcore"
version = "1.3.0"
dependencies = [
"bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -253,7 +267,7 @@ dependencies = [
"nix 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rocksdb 0.4.5",
"rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -738,14 +752,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rocksdb"
version = "0.4.5"
source = "git+https://github.com/ethcore/rust-rocksdb#eadce7f74cfe92b99ce63a77af425b47857239b8"
dependencies = [
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
"rocksdb-sys 0.3.0",
"rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)",
]
[[package]]
name = "rocksdb-sys"
version = "0.3.0"
source = "git+https://github.com/ethcore/rust-rocksdb#eadce7f74cfe92b99ce63a77af425b47857239b8"
dependencies = [
"gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -19,7 +19,7 @@
use std::collections::HashMap;
use util::{U256, H256, Address, Bytes, FixedHash};
use ethcore::client::EnvInfo;
use ethcore::evm::{self, Ext, ContractCreateResult, MessageCallResult, Schedule};
use ethcore::evm::{self, Ext, ContractCreateResult, MessageCallResult, Schedule, CallType};
pub struct FakeExt {
schedule: Schedule,
@ -67,7 +67,8 @@ impl Ext for FakeExt {
_value: Option<U256>,
_data: &[u8],
_code_address: &Address,
_output: &mut [u8]) -> MessageCallResult {
_output: &mut [u8],
_call_type: CallType) -> MessageCallResult {
unimplemented!();
}