diff --git a/Cargo.lock b/Cargo.lock index 80a7ef297..fcbb12ab3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2253,7 +2253,7 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.15.1" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3417,7 +3417,7 @@ dependencies = [ "ethcore-logger 1.9.0", "ethcore-util 1.9.0", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)", "vm 0.1.0", "wasm-utils 0.1.0 (git+https://github.com/paritytech/wasm-utils)", ] @@ -3433,7 +3433,7 @@ dependencies = [ "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3647,7 +3647,7 @@ dependencies = [ "checksum parity-tokio-ipc 0.1.5 (git+https://github.com/nikvolf/parity-tokio-ipc)" = "" "checksum parity-ui-old-precompiled 1.8.0 (git+https://github.com/paritytech/js-precompiled.git?branch=v1)" = "" "checksum parity-ui-precompiled 1.9.0 (git+https://github.com/paritytech/js-precompiled.git)" = "" -"checksum parity-wasm 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "95f6243c2d6fadf903b5edfd0011817efc20522ce5f360abf4648c24ea87581a" +"checksum parity-wasm 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8431a184ad88cfbcd71a792aaca319cc7203a94300c26b8dce2d0df0681ea87d" "checksum parity-wordlist 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81451bfab101d186f8fc4a0aa13cb5539b31b02c4ed96425a0842e2a413daba6" "checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" "checksum parking_lot_core 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4f610cb9664da38e417ea3225f23051f589851999535290e077939838ab7a595" diff --git a/ethcore/res/wasm-tests b/ethcore/res/wasm-tests index 94b7877b5..9a1fcbf0d 160000 --- a/ethcore/res/wasm-tests +++ b/ethcore/res/wasm-tests @@ -1 +1 @@ -Subproject commit 94b7877b5826a53627b8732ea0feb45869dd04ab +Subproject commit 9a1fcbf0d4e73bea437577e807bc38c7ba243d80 diff --git a/ethcore/vm/src/schedule.rs b/ethcore/vm/src/schedule.rs index 21924afea..e250bfa1c 100644 --- a/ethcore/vm/src/schedule.rs +++ b/ethcore/vm/src/schedule.rs @@ -127,12 +127,14 @@ pub struct WasmCosts { pub mul: u32, /// Memory (load/store) operations multiplier. pub mem: u32, - /// Memory copy operation. + /// Memory copy operation, per byte. pub mem_copy: u32, + /// Memory move operation, per byte. + pub mem_move: u32, + /// Memory set operation, per byte. + pub mem_set: u32, /// Static region charge, per byte. pub static_region: u32, - /// General static query of u64 value from env-info - pub static_u64: u32, /// General static query of U256 value from env-info pub static_u256: u32, /// General static query of Address value from env-info @@ -147,11 +149,9 @@ impl Default for WasmCosts { mul: 4, mem: 2, mem_copy: 1, + mem_move: 1, + mem_set: 1, static_region: 1, - - // due to runtime issues, this can be slow - static_u64: 32, - static_u256: 64, static_address: 40, } diff --git a/ethcore/wasm/src/env.rs b/ethcore/wasm/src/env.rs index 4a518beec..1a2f5fd3b 100644 --- a/ethcore/wasm/src/env.rs +++ b/ethcore/wasm/src/env.rs @@ -38,12 +38,12 @@ pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[ None, ), Static( - "_malloc", + "_ext_malloc", &[I32], Some(I32), ), Static( - "_free", + "_ext_free", &[I32], None, ), @@ -92,6 +92,21 @@ pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[ &[I32; 3], Some(I32), ), + Static( + "_ext_memcpy", + &[I32; 3], + Some(I32), + ), + Static( + "_ext_memset", + &[I32; 3], + Some(I32), + ), + Static( + "_ext_memmove", + &[I32; 3], + Some(I32), + ), Static( "_panic", &[I32; 2], @@ -99,7 +114,7 @@ pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[ ), Static( "_blockhash", - &[I32; 3], + &[I64, I32], Some(I32), ), Static( @@ -130,12 +145,12 @@ pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[ Static( "_timestamp", &[], - Some(I32), + Some(I64), ), Static( "_blocknumber", &[], - Some(I32), + Some(I64), ), Static( "_difficulty", @@ -162,8 +177,8 @@ pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[ Static( "_llvm_bswap_i64", - &[I32; 2], - Some(I32) + &[I64], + Some(I64) ), ]; diff --git a/ethcore/wasm/src/runtime.rs b/ethcore/wasm/src/runtime.rs index 39b239fe8..e5a6dc910 100644 --- a/ethcore/wasm/src/runtime.rs +++ b/ethcore/wasm/src/runtime.rs @@ -560,32 +560,67 @@ impl<'a, 'b> Runtime<'a, 'b> { fn mem_copy(&mut self, context: InterpreterCallerContext) -> Result, InterpreterError> { + // + // method signature: + // fn memcpy(dest: *const u8, src: *const u8, len: u32) -> *mut u8; + // + let len = context.value_stack.pop_as::()? as u32; - let dst = context.value_stack.pop_as::()? as u32; let src = context.value_stack.pop_as::()? as u32; + let dst = context.value_stack.pop_as::()? as u32; self.charge(|schedule| schedule.wasm.mem_copy as u64 * len as u64)?; - let mem = self.memory().get(src, len as usize)?; - self.memory().set(dst, &mem)?; + self.memory().copy_nonoverlapping(src as usize, dst as usize, len as usize)?; - Ok(Some(0i32.into())) + Ok(Some(Into::into(dst as i32))) } - fn bswap_32(x: u32) -> u32 { - x >> 24 | x >> 8 & 0xff00 | x << 8 & 0xff0000 | x << 24 + fn mem_move(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> + { + // + // method signature: + // fn memmove(dest: *const u8, src: *const u8, len: u32) -> *mut u8; + // + + let len = context.value_stack.pop_as::()? as u32; + let src = context.value_stack.pop_as::()? as u32; + let dst = context.value_stack.pop_as::()? as u32; + + self.charge(|schedule| schedule.wasm.mem_move as u64 * len as u64)?; + + self.memory().copy(src as usize, dst as usize, len as usize)?; + + Ok(Some(Into::into(dst as i32))) + } + + fn mem_set(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> + { + // + // method signature: + // fn memset(dest: *const u8, c: u32, len: u32) -> *mut u8; + // + + let len = context.value_stack.pop_as::()? as u32; + let c = context.value_stack.pop_as::()? as u32; + let dst = context.value_stack.pop_as::()? as u32; + + self.charge(|schedule| schedule.wasm.mem_set as u64 * len as u64)?; + + self.memory().clear(dst as usize, c as u8, len as usize)?; + + Ok(Some(Into::into(dst as i32))) } fn bitswap_i64(&mut self, context: InterpreterCallerContext) -> Result, InterpreterError> { - let x1 = context.value_stack.pop_as::()?; - let x2 = context.value_stack.pop_as::()?; + let x = context.value_stack.pop_as::()?; + let result = x.swap_bytes(); - let result = ((Runtime::bswap_32(x2 as u32) as u64) << 32 - | Runtime::bswap_32(x1 as u32) as u64) as i64; - - self.return_i64(result) + Ok(Some(result.into())) } fn user_panic(&mut self, context: InterpreterCallerContext) @@ -606,13 +641,10 @@ impl<'a, 'b> Runtime<'a, 'b> { -> Result, InterpreterError> { let return_ptr = context.value_stack.pop_as::()? as u32; - let block_hi = context.value_stack.pop_as::()? as u32; - let block_lo = context.value_stack.pop_as::()? as u32; + let block_num = context.value_stack.pop_as::()? as u64; self.charge(|schedule| schedule.blockhash_gas as u64)?; - let block_num = (block_hi as u64) << 32 | block_lo as u64; - trace!("Requesting block hash for block #{}", block_num); let hash = self.ext.blockhash(&U256::from(block_num)); @@ -694,14 +726,14 @@ impl<'a, 'b> Runtime<'a, 'b> { -> Result, InterpreterError> { let timestamp = self.ext.env_info().timestamp as i64; - self.return_i64(timestamp) + Ok(Some(timestamp.into())) } fn block_number(&mut self, _context: InterpreterCallerContext) -> Result, InterpreterError> { - let block_number: u64 = self.ext.env_info().number.into(); - self.return_i64(block_number as i64) + let block_number = self.ext.env_info().number as i64; + Ok(Some(block_number.into())) } fn difficulty(&mut self, context: InterpreterCallerContext) @@ -726,25 +758,6 @@ impl<'a, 'b> Runtime<'a, 'b> { Ok(None) } - fn return_i64(&mut self, val: i64) -> Result, InterpreterError> { - self.charge(|schedule| schedule.wasm.static_u64 as u64)?; - - let uval = val as u64; - let hi = (uval >> 32) as i32; - let lo = (uval << 32 >> 32) as i32; - - let target = self.instance.module("contract").ok_or(UserTrap::Other)?; - target.execute_export( - "setTempRet0", - self.execution_params().add_argument( - interpreter::RuntimeValue::I32(hi).into() - ), - )?; - Ok(Some( - (lo).into() - )) - } - pub fn execution_params(&mut self) -> interpreter::ExecutionParams { use super::env; @@ -812,10 +825,10 @@ impl<'a, 'b> interpreter::UserFunctionExecutor for Runtime<'a, 'b> { -> Result, InterpreterError> { match name { - "_malloc" => { + "_ext_malloc" => { self.malloc(context) }, - "_free" => { + "_ext_free" => { // Since it is arena allocator, free does nothing // todo: update if changed self.user_noop(context) @@ -853,6 +866,15 @@ impl<'a, 'b> interpreter::UserFunctionExecutor for Runtime<'a, 'b> { "_emscripten_memcpy_big" => { self.mem_copy(context) }, + "_ext_memcpy" => { + self.mem_copy(context) + }, + "_ext_memmove" => { + self.mem_move(context) + }, + "_ext_memset" => { + self.mem_set(context) + }, "_llvm_bswap_i64" => { self.bitswap_i64(context) }, diff --git a/ethcore/wasm/src/tests.rs b/ethcore/wasm/src/tests.rs index 245f27536..0a93be11c 100644 --- a/ethcore/wasm/src/tests.rs +++ b/ethcore/wasm/src/tests.rs @@ -60,7 +60,7 @@ fn empty() { test_finalize(interpreter.exec(params, &mut ext)).unwrap() }; - assert_eq!(gas_left, U256::from(99_976)); + assert_eq!(gas_left, U256::from(99_982)); } // This test checks if the contract deserializes payload header properly. @@ -89,7 +89,6 @@ fn logger() { test_finalize(interpreter.exec(params, &mut ext)).unwrap() }; - assert_eq!(gas_left, U256::from(15_177)); let address_val: H256 = address.into(); assert_eq!( ext.store.get(&"0100000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"), @@ -113,6 +112,7 @@ fn logger() { U256::from(1_000_000_000), "Logger sets 0x04 key to the trasferred value" ); + assert_eq!(gas_left, U256::from(19_143)); } // This test checks if the contract can allocate memory and pass pointer to the result stream properly. @@ -142,13 +142,12 @@ fn identity() { } }; - assert_eq!(gas_left, U256::from(99_695)); - assert_eq!( Address::from_slice(&result), sender, "Idenity test contract does not return the sender passed" ); + assert_eq!(gas_left, U256::from(99_844)); } // Dispersion test sends byte array and expect the contract to 'disperse' the original elements with @@ -176,12 +175,12 @@ fn dispersion() { } }; - assert_eq!(gas_left, U256::from(96_543)); assert_eq!( result, vec![0u8, 0, 125, 11, 197, 7, 255, 8, 19, 0] ); + assert_eq!(gas_left, U256::from(99_469)); } #[test] @@ -205,12 +204,11 @@ fn suicide_not() { } }; - assert_eq!(gas_left, U256::from(96_822)); - assert_eq!( result, vec![0u8] ); + assert_eq!(gas_left, U256::from(99_724)); } #[test] @@ -241,8 +239,8 @@ fn suicide() { } }; - assert_eq!(gas_left, U256::from(96_580)); assert!(ext.suicides.contains(&refund)); + assert_eq!(gas_left, U256::from(99_663)); } #[test] @@ -272,7 +270,7 @@ fn create() { assert!(ext.calls.contains( &FakeCall { call_type: FakeCallType::Create, - gas: U256::from(62_324), + gas: U256::from(65_903), sender_address: None, receive_address: None, value: Some(1_000_000_000.into()), @@ -280,7 +278,7 @@ fn create() { code_address: None, } )); - assert_eq!(gas_left, U256::from(62_289)); + assert_eq!(gas_left, U256::from(65_896)); } @@ -314,7 +312,7 @@ fn call_code() { assert!(ext.calls.contains( &FakeCall { call_type: FakeCallType::Call, - gas: U256::from(95_585), + gas: U256::from(98_709), sender_address: Some(sender), receive_address: Some(receiver), value: None, @@ -322,11 +320,11 @@ fn call_code() { code_address: Some("0d13710000000000000000000000000000000000".parse().unwrap()), } )); - assert_eq!(gas_left, U256::from(90_665)); // siphash result let res = LittleEndian::read_u32(&result[..]); assert_eq!(res, 4198595614); + assert_eq!(gas_left, U256::from(93_851)); } #[test] @@ -359,7 +357,7 @@ fn call_static() { assert!(ext.calls.contains( &FakeCall { call_type: FakeCallType::Call, - gas: U256::from(95_585), + gas: U256::from(98_709), sender_address: Some(sender), receive_address: Some(receiver), value: None, @@ -367,11 +365,12 @@ fn call_static() { code_address: Some("13077bfb00000000000000000000000000000000".parse().unwrap()), } )); - assert_eq!(gas_left, U256::from(90_665)); // siphash result let res = LittleEndian::read_u32(&result[..]); assert_eq!(res, 317632590); + + assert_eq!(gas_left, U256::from(93_851)); } // Realloc test @@ -393,8 +392,8 @@ fn realloc() { GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), } }; - assert_eq!(gas_left, U256::from(96_811)); assert_eq!(result, vec![0u8; 2]); + assert_eq!(gas_left, U256::from(99_787)); } // Tests that contract's ability to read from a storage @@ -419,8 +418,8 @@ fn storage_read() { } }; - assert_eq!(gas_left, U256::from(96_645)); assert_eq!(Address::from(&result[12..32]), address); + assert_eq!(gas_left, U256::from(99_702)); } // Tests keccak calculation @@ -446,9 +445,97 @@ fn keccak() { }; assert_eq!(H256::from_slice(&result), H256::from("68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87")); - assert_eq!(gas_left, U256::from(80_452)); + assert_eq!(gas_left, U256::from(84_520)); } +// memcpy test. +#[test] +fn memcpy() { + ::ethcore_logger::init_log(); + let code = load_sample!("mem.wasm"); + + let mut test_payload = Vec::with_capacity(8192); + for i in 0..8192 { + test_payload.push((i % 255) as u8); + } + let mut data = vec![0u8]; + data.extend(&test_payload); + + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + params.data = Some(data); + let mut ext = FakeExt::new(); + + let (gas_left, result) = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(_) => { panic!("mem should return payload"); }, + GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), + } + }; + + assert_eq!(result, test_payload); + assert_eq!(gas_left, U256::from(75_324)); +} + +// memmove test. +#[test] +fn memmove() { + ::ethcore_logger::init_log(); + let code = load_sample!("mem.wasm"); + + let mut test_payload = Vec::with_capacity(8192); + for i in 0..8192 { + test_payload.push((i % 255) as u8); + } + let mut data = vec![1u8]; + data.extend(&test_payload); + + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + params.data = Some(data); + let mut ext = FakeExt::new(); + + let (gas_left, result) = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(_) => { panic!("mem should return payload"); }, + GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), + } + }; + + assert_eq!(result, test_payload); + assert_eq!(gas_left, U256::from(75_324)); +} + +// memset test +#[test] +fn memset() { + ::ethcore_logger::init_log(); + let code = load_sample!("mem.wasm"); + + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + params.data = Some(vec![2u8, 228u8]); + let mut ext = FakeExt::new(); + + let (gas_left, result) = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(_) => { panic!("mem should return payload"); }, + GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), + } + }; + + assert_eq!(result, vec![228u8; 8192]); + assert_eq!(gas_left, U256::from(75_324)); +} macro_rules! reqrep_test { ($name: expr, $input: expr) => { @@ -500,11 +587,11 @@ fn math_add() { } ).expect("Interpreter to execute without any errors"); - assert_eq!(gas_left, U256::from(94_666)); assert_eq!( U256::from_dec_str("1888888888888888888888888888887").unwrap(), (&result[..]).into() ); + assert_eq!(gas_left, U256::from(98_576)); } // multiplication @@ -522,11 +609,11 @@ fn math_mul() { } ).expect("Interpreter to execute without any errors"); - assert_eq!(gas_left, U256::from(93_719)); assert_eq!( U256::from_dec_str("888888888888888888888888888887111111111111111111111111111112").unwrap(), (&result[..]).into() ); + assert_eq!(gas_left, U256::from(97_726)); } // subtraction @@ -544,11 +631,11 @@ fn math_sub() { } ).expect("Interpreter to execute without any errors"); - assert_eq!(gas_left, U256::from(94_718)); assert_eq!( U256::from_dec_str("111111111111111111111111111111").unwrap(), (&result[..]).into() ); + assert_eq!(gas_left, U256::from(98_568)); } // subtraction with overflow @@ -566,7 +653,10 @@ fn math_sub_with_overflow() { } ); - assert_eq!(result, Err(vm::Error::Wasm("Wasm runtime error: User(Panic(\"arithmetic operation overflow\"))".into()))); + match result { + Err(vm::Error::Wasm(_)) => {}, + _ => panic!("Unexpected result {:?}", result), + } } #[test] @@ -583,11 +673,11 @@ fn math_div() { } ).expect("Interpreter to execute without any errors"); - assert_eq!(gas_left, U256::from(86_996)); assert_eq!( U256::from_dec_str("1125000").unwrap(), (&result[..]).into() ); + assert_eq!(gas_left, U256::from(91_564)); } // This test checks the ability of wasm contract to invoke @@ -675,7 +765,7 @@ fn externs() { "Gas limit requested and returned does not match" ); - assert_eq!(gas_left, U256::from(91_857)); + assert_eq!(gas_left, U256::from(97_740)); } #[test] @@ -701,7 +791,7 @@ fn embedded_keccak() { }; assert_eq!(H256::from_slice(&result), H256::from("68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87")); - assert_eq!(gas_left, U256::from(80_452)); + assert_eq!(gas_left, U256::from(84_520)); } /// This test checks the correctness of log extern @@ -736,5 +826,5 @@ fn events() { assert_eq!(&log_entry.data, b"gnihtemos"); assert_eq!(&result, b"gnihtemos"); - assert_eq!(gas_left, U256::from(78039)); -} \ No newline at end of file + assert_eq!(gas_left, U256::from(82_721)); +}