mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-20 08:05:41 +00:00
WASM runtime switch to import memory (#4737)
* WASM runtime switch to import memory Up to now runtimes have exported their memory. To unify it with sandboxing, this pr switches runtimes to import memory as well. From a functional perspective, exporting/importing memory makes no difference to the runtime. To provide backwards compatibility, WASM exported memory is still supported. * Revert debug stuff * Revert some stuff
This commit is contained in:
Generated
+6
-6
@@ -3234,7 +3234,7 @@ dependencies = [
|
|||||||
"sp-std 2.0.0",
|
"sp-std 2.0.0",
|
||||||
"sp-transaction-pool 2.0.0",
|
"sp-transaction-pool 2.0.0",
|
||||||
"sp-version 2.0.0",
|
"sp-version 2.0.0",
|
||||||
"substrate-wasm-builder-runner 1.0.4",
|
"substrate-wasm-builder-runner 1.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3295,7 +3295,7 @@ dependencies = [
|
|||||||
"sp-std 2.0.0",
|
"sp-std 2.0.0",
|
||||||
"sp-transaction-pool 2.0.0",
|
"sp-transaction-pool 2.0.0",
|
||||||
"sp-version 2.0.0",
|
"sp-version 2.0.0",
|
||||||
"substrate-wasm-builder-runner 1.0.4",
|
"substrate-wasm-builder-runner 1.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5823,7 +5823,7 @@ dependencies = [
|
|||||||
"sp-runtime 2.0.0",
|
"sp-runtime 2.0.0",
|
||||||
"sp-sandbox 0.8.0",
|
"sp-sandbox 0.8.0",
|
||||||
"sp-std 2.0.0",
|
"sp-std 2.0.0",
|
||||||
"substrate-wasm-builder-runner 1.0.4",
|
"substrate-wasm-builder-runner 1.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6693,7 +6693,7 @@ dependencies = [
|
|||||||
"sp-io 2.0.0",
|
"sp-io 2.0.0",
|
||||||
"sp-runtime-interface 2.0.0",
|
"sp-runtime-interface 2.0.0",
|
||||||
"sp-std 2.0.0",
|
"sp-std 2.0.0",
|
||||||
"substrate-wasm-builder-runner 1.0.4",
|
"substrate-wasm-builder-runner 1.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7063,7 +7063,7 @@ dependencies = [
|
|||||||
"sp-trie 2.0.0",
|
"sp-trie 2.0.0",
|
||||||
"sp-version 2.0.0",
|
"sp-version 2.0.0",
|
||||||
"substrate-test-runtime-client 2.0.0",
|
"substrate-test-runtime-client 2.0.0",
|
||||||
"substrate-wasm-builder-runner 1.0.4",
|
"substrate-wasm-builder-runner 1.0.5",
|
||||||
"trie-db 0.19.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"trie-db 0.19.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7104,7 +7104,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "substrate-wasm-builder-runner"
|
name = "substrate-wasm-builder-runner"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource};
|
use wasm_builder_runner::WasmBuilder;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
build_current_project_with_rustflags(
|
WasmBuilder::new()
|
||||||
"wasm_binary.rs",
|
.with_current_project()
|
||||||
WasmBuilderSource::Crates("1.0.9"),
|
.with_wasm_builder_from_crates("1.0.9")
|
||||||
// This instructs LLD to export __heap_base as a global variable, which is used by the
|
.export_heap_base()
|
||||||
// external memory allocator.
|
.import_memory()
|
||||||
"-Clink-arg=--export=__heap_base",
|
.build()
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,17 +14,13 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource};
|
use wasm_builder_runner::WasmBuilder;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
build_current_project_with_rustflags(
|
WasmBuilder::new()
|
||||||
"wasm_binary.rs",
|
.with_current_project()
|
||||||
WasmBuilderSource::CratesOrPath {
|
.with_wasm_builder_from_crates_or_path("1.0.9", "../../../utils/wasm-builder")
|
||||||
path: "../../../utils/wasm-builder",
|
.export_heap_base()
|
||||||
version: "1.0.9",
|
.import_memory()
|
||||||
},
|
.build()
|
||||||
// This instructs LLD to export __heap_base as a global variable, which is used by the
|
|
||||||
// external memory allocator.
|
|
||||||
"-Clink-arg=--export=__heap_base",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,17 +14,13 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource};
|
use wasm_builder_runner::WasmBuilder;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
build_current_project_with_rustflags(
|
WasmBuilder::new()
|
||||||
"wasm_binary.rs",
|
.with_current_project()
|
||||||
WasmBuilderSource::CratesOrPath {
|
.with_wasm_builder_from_crates_or_path("1.0.9", "../../../utils/wasm-builder")
|
||||||
path: "../../../utils/wasm-builder",
|
.export_heap_base()
|
||||||
version: "1.0.9",
|
.import_memory()
|
||||||
},
|
.build()
|
||||||
// This instructs LLD to export __heap_base as a global variable, which is used by the
|
|
||||||
// external memory allocator.
|
|
||||||
"-Clink-arg=--export=__heap_base",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ extern "C" {
|
|||||||
fn yet_another_missing_external();
|
fn yet_another_missing_external();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
/// Mutable static variables should be always observed to have
|
||||||
|
/// the initialized value at the start of a runtime call.
|
||||||
|
static mut MUTABLE_STATIC: u64 = 32;
|
||||||
|
|
||||||
sp_core::wasm_export_functions! {
|
sp_core::wasm_export_functions! {
|
||||||
fn test_calling_missing_external() {
|
fn test_calling_missing_external() {
|
||||||
unsafe { missing_external() }
|
unsafe { missing_external() }
|
||||||
@@ -217,6 +222,41 @@ sp_core::wasm_export_functions! {
|
|||||||
fn test_sp_allocator_compiles() {
|
fn test_sp_allocator_compiles() {
|
||||||
sp_allocator::FreeingBumpHeapAllocator::new(0);
|
sp_allocator::FreeingBumpHeapAllocator::new(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn returns_mutable_static() -> u64 {
|
||||||
|
unsafe {
|
||||||
|
MUTABLE_STATIC += 1;
|
||||||
|
MUTABLE_STATIC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocates_huge_stack_array(trap: bool) -> Vec<u8> {
|
||||||
|
// Allocate a stack frame that is approx. 75% of the stack (assuming it is 1MB).
|
||||||
|
// This will just decrease (stacks in wasm32-u-u grow downwards) the stack
|
||||||
|
// pointer. This won't trap on the current compilers.
|
||||||
|
let mut data = [0u8; 1024 * 768];
|
||||||
|
|
||||||
|
// Then make sure we actually write something to it.
|
||||||
|
//
|
||||||
|
// If:
|
||||||
|
// 1. the stack area is placed at the beginning of the linear memory space, and
|
||||||
|
// 2. the stack pointer points to out-of-bounds area, and
|
||||||
|
// 3. a write is performed around the current stack pointer.
|
||||||
|
//
|
||||||
|
// then a trap should happen.
|
||||||
|
//
|
||||||
|
for (i, v) in data.iter_mut().enumerate() {
|
||||||
|
*v = i as u8; // deliberate truncation
|
||||||
|
}
|
||||||
|
|
||||||
|
if trap {
|
||||||
|
// There is a small chance of this to be pulled up in theory. In practice
|
||||||
|
// the probability of that is rather low.
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
|
||||||
|
data.to_vec()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ use sc_runtime_test::WASM_BINARY;
|
|||||||
use sp_state_machine::TestExternalities as CoreTestExternalities;
|
use sp_state_machine::TestExternalities as CoreTestExternalities;
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
use sp_trie::{TrieConfiguration, trie_types::Layout};
|
use sp_trie::{TrieConfiguration, trie_types::Layout};
|
||||||
|
use sp_wasm_interface::HostFunctions as _;
|
||||||
|
|
||||||
use crate::WasmExecutionMethod;
|
use crate::WasmExecutionMethod;
|
||||||
|
|
||||||
@@ -38,16 +39,14 @@ fn call_in_wasm<E: Externalities>(
|
|||||||
call_data: &[u8],
|
call_data: &[u8],
|
||||||
execution_method: WasmExecutionMethod,
|
execution_method: WasmExecutionMethod,
|
||||||
ext: &mut E,
|
ext: &mut E,
|
||||||
code: &[u8],
|
|
||||||
heap_pages: u64,
|
|
||||||
) -> crate::error::Result<Vec<u8>> {
|
) -> crate::error::Result<Vec<u8>> {
|
||||||
crate::call_in_wasm::<HostFunctions>(
|
crate::call_in_wasm::<HostFunctions>(
|
||||||
function,
|
function,
|
||||||
call_data,
|
call_data,
|
||||||
execution_method,
|
execution_method,
|
||||||
ext,
|
ext,
|
||||||
code,
|
&WASM_BINARY[..],
|
||||||
heap_pages,
|
1024,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -57,15 +56,12 @@ fn call_in_wasm<E: Externalities>(
|
|||||||
fn returning_should_work(wasm_method: WasmExecutionMethod) {
|
fn returning_should_work(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let output = call_in_wasm(
|
let output = call_in_wasm(
|
||||||
"test_empty_return",
|
"test_empty_return",
|
||||||
&[],
|
&[],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
assert_eq!(output, vec![0u8; 0]);
|
assert_eq!(output, vec![0u8; 0]);
|
||||||
}
|
}
|
||||||
@@ -73,17 +69,14 @@ fn returning_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
#[test_case(WasmExecutionMethod::Interpreted)]
|
#[test_case(WasmExecutionMethod::Interpreted)]
|
||||||
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
||||||
fn call_not_existing_function(wasm_method: WasmExecutionMethod) {
|
fn call_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
match call_in_wasm(
|
match call_in_wasm(
|
||||||
"test_calling_missing_external",
|
"test_calling_missing_external",
|
||||||
&[],
|
&[],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
) {
|
) {
|
||||||
Ok(_) => panic!("was expected an `Err`"),
|
Ok(_) => panic!("was expected an `Err`"),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -107,15 +100,12 @@ fn call_not_existing_function(wasm_method: WasmExecutionMethod) {
|
|||||||
fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) {
|
fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
match call_in_wasm(
|
match call_in_wasm(
|
||||||
"test_calling_yet_another_missing_external",
|
"test_calling_yet_another_missing_external",
|
||||||
&[],
|
&[],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
) {
|
) {
|
||||||
Ok(_) => panic!("was expected an `Err`"),
|
Ok(_) => panic!("was expected an `Err`"),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -139,15 +129,12 @@ fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) {
|
|||||||
fn panicking_should_work(wasm_method: WasmExecutionMethod) {
|
fn panicking_should_work(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let output = call_in_wasm(
|
let output = call_in_wasm(
|
||||||
"test_panic",
|
"test_panic",
|
||||||
&[],
|
&[],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
);
|
);
|
||||||
assert!(output.is_err());
|
assert!(output.is_err());
|
||||||
|
|
||||||
@@ -156,8 +143,6 @@ fn panicking_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&[0],
|
&[0],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
);
|
);
|
||||||
assert_eq!(Decode::decode(&mut &output.unwrap()[..]), Ok(Vec::<u8>::new()));
|
assert_eq!(Decode::decode(&mut &output.unwrap()[..]), Ok(Vec::<u8>::new()));
|
||||||
|
|
||||||
@@ -166,8 +151,6 @@ fn panicking_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&vec![2].encode(),
|
&vec![2].encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
);
|
);
|
||||||
assert!(output.is_err());
|
assert!(output.is_err());
|
||||||
}
|
}
|
||||||
@@ -180,15 +163,12 @@ fn storage_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
{
|
{
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
ext.set_storage(b"foo".to_vec(), b"bar".to_vec());
|
ext.set_storage(b"foo".to_vec(), b"bar".to_vec());
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let output = call_in_wasm(
|
let output = call_in_wasm(
|
||||||
"test_data_in",
|
"test_data_in",
|
||||||
&b"Hello world".to_vec().encode(),
|
&b"Hello world".to_vec().encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
assert_eq!(output, b"all ok!".to_vec().encode());
|
assert_eq!(output, b"all ok!".to_vec().encode());
|
||||||
@@ -216,7 +196,6 @@ fn clear_prefix_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
ext.set_storage(b"aba".to_vec(), b"3".to_vec());
|
ext.set_storage(b"aba".to_vec(), b"3".to_vec());
|
||||||
ext.set_storage(b"abb".to_vec(), b"4".to_vec());
|
ext.set_storage(b"abb".to_vec(), b"4".to_vec());
|
||||||
ext.set_storage(b"bbb".to_vec(), b"5".to_vec());
|
ext.set_storage(b"bbb".to_vec(), b"5".to_vec());
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
// This will clear all entries which prefix is "ab".
|
// This will clear all entries which prefix is "ab".
|
||||||
let output = call_in_wasm(
|
let output = call_in_wasm(
|
||||||
@@ -224,8 +203,6 @@ fn clear_prefix_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&b"ab".to_vec().encode(),
|
&b"ab".to_vec().encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
assert_eq!(output, b"all ok!".to_vec().encode());
|
assert_eq!(output, b"all ok!".to_vec().encode());
|
||||||
@@ -247,15 +224,12 @@ fn clear_prefix_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
fn blake2_256_should_work(wasm_method: WasmExecutionMethod) {
|
fn blake2_256_should_work(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
call_in_wasm(
|
call_in_wasm(
|
||||||
"test_blake2_256",
|
"test_blake2_256",
|
||||||
&[0],
|
&[0],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
blake2_256(&b""[..]).to_vec().encode(),
|
blake2_256(&b""[..]).to_vec().encode(),
|
||||||
);
|
);
|
||||||
@@ -265,8 +239,6 @@ fn blake2_256_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&b"Hello world!".to_vec().encode(),
|
&b"Hello world!".to_vec().encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
blake2_256(&b"Hello world!"[..]).to_vec().encode(),
|
blake2_256(&b"Hello world!"[..]).to_vec().encode(),
|
||||||
);
|
);
|
||||||
@@ -277,15 +249,12 @@ fn blake2_256_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
fn blake2_128_should_work(wasm_method: WasmExecutionMethod) {
|
fn blake2_128_should_work(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
call_in_wasm(
|
call_in_wasm(
|
||||||
"test_blake2_128",
|
"test_blake2_128",
|
||||||
&[0],
|
&[0],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
blake2_128(&b""[..]).to_vec().encode(),
|
blake2_128(&b""[..]).to_vec().encode(),
|
||||||
);
|
);
|
||||||
@@ -295,8 +264,6 @@ fn blake2_128_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&b"Hello world!".to_vec().encode(),
|
&b"Hello world!".to_vec().encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
blake2_128(&b"Hello world!"[..]).to_vec().encode(),
|
blake2_128(&b"Hello world!"[..]).to_vec().encode(),
|
||||||
);
|
);
|
||||||
@@ -307,15 +274,12 @@ fn blake2_128_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
fn sha2_256_should_work(wasm_method: WasmExecutionMethod) {
|
fn sha2_256_should_work(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
call_in_wasm(
|
call_in_wasm(
|
||||||
"test_sha2_256",
|
"test_sha2_256",
|
||||||
&[0],
|
&[0],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
|
hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
|
||||||
@@ -328,8 +292,6 @@ fn sha2_256_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&b"Hello world!".to_vec().encode(),
|
&b"Hello world!".to_vec().encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
hex!("c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a")
|
hex!("c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a")
|
||||||
@@ -343,15 +305,12 @@ fn sha2_256_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
fn twox_256_should_work(wasm_method: WasmExecutionMethod) {
|
fn twox_256_should_work(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
call_in_wasm(
|
call_in_wasm(
|
||||||
"test_twox_256",
|
"test_twox_256",
|
||||||
&[0],
|
&[0],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
hex!(
|
hex!(
|
||||||
"99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a"
|
"99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a"
|
||||||
@@ -363,8 +322,6 @@ fn twox_256_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&b"Hello world!".to_vec().encode(),
|
&b"Hello world!".to_vec().encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
hex!(
|
hex!(
|
||||||
"b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74"
|
"b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74"
|
||||||
@@ -377,15 +334,12 @@ fn twox_256_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
fn twox_128_should_work(wasm_method: WasmExecutionMethod) {
|
fn twox_128_should_work(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
call_in_wasm(
|
call_in_wasm(
|
||||||
"test_twox_128",
|
"test_twox_128",
|
||||||
&[0],
|
&[0],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
hex!("99e9d85137db46ef4bbea33613baafd5").to_vec().encode(),
|
hex!("99e9d85137db46ef4bbea33613baafd5").to_vec().encode(),
|
||||||
);
|
);
|
||||||
@@ -395,8 +349,6 @@ fn twox_128_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&b"Hello world!".to_vec().encode(),
|
&b"Hello world!".to_vec().encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
hex!("b27dfd7f223f177f2a13647b533599af").to_vec().encode(),
|
hex!("b27dfd7f223f177f2a13647b533599af").to_vec().encode(),
|
||||||
);
|
);
|
||||||
@@ -407,7 +359,6 @@ fn twox_128_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
fn ed25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
fn ed25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
let key = ed25519::Pair::from_seed(&blake2_256(b"test"));
|
let key = ed25519::Pair::from_seed(&blake2_256(b"test"));
|
||||||
let sig = key.sign(b"all ok!");
|
let sig = key.sign(b"all ok!");
|
||||||
let mut calldata = vec![];
|
let mut calldata = vec![];
|
||||||
@@ -420,8 +371,6 @@ fn ed25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&calldata.encode(),
|
&calldata.encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
true.encode(),
|
true.encode(),
|
||||||
);
|
);
|
||||||
@@ -437,8 +386,6 @@ fn ed25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&calldata.encode(),
|
&calldata.encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
false.encode(),
|
false.encode(),
|
||||||
);
|
);
|
||||||
@@ -449,7 +396,6 @@ fn ed25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
fn sr25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
fn sr25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
let key = sr25519::Pair::from_seed(&blake2_256(b"test"));
|
let key = sr25519::Pair::from_seed(&blake2_256(b"test"));
|
||||||
let sig = key.sign(b"all ok!");
|
let sig = key.sign(b"all ok!");
|
||||||
let mut calldata = vec![];
|
let mut calldata = vec![];
|
||||||
@@ -462,8 +408,6 @@ fn sr25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&calldata.encode(),
|
&calldata.encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
true.encode(),
|
true.encode(),
|
||||||
);
|
);
|
||||||
@@ -479,8 +423,6 @@ fn sr25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&calldata.encode(),
|
&calldata.encode(),
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
false.encode(),
|
false.encode(),
|
||||||
);
|
);
|
||||||
@@ -490,17 +432,13 @@ fn sr25519_verify_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
||||||
fn ordered_trie_root_should_work(wasm_method: WasmExecutionMethod) {
|
fn ordered_trie_root_should_work(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
|
||||||
let trie_input = vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()];
|
let trie_input = vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()];
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
call_in_wasm(
|
call_in_wasm(
|
||||||
"test_ordered_trie_root",
|
"test_ordered_trie_root",
|
||||||
&[0],
|
&[0],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext.ext(),
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
Layout::<Blake2Hasher>::ordered_trie_root(trie_input.iter()).as_bytes().encode(),
|
Layout::<Blake2Hasher>::ordered_trie_root(trie_input.iter()).as_bytes().encode(),
|
||||||
);
|
);
|
||||||
@@ -514,16 +452,12 @@ fn offchain_local_storage_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let (offchain, state) = testing::TestOffchainExt::new();
|
let (offchain, state) = testing::TestOffchainExt::new();
|
||||||
ext.register_extension(OffchainExt::new(offchain));
|
ext.register_extension(OffchainExt::new(offchain));
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
let mut ext = ext.ext();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
call_in_wasm(
|
call_in_wasm(
|
||||||
"test_offchain_local_storage",
|
"test_offchain_local_storage",
|
||||||
&[0],
|
&[0],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext.ext(),
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
true.encode(),
|
true.encode(),
|
||||||
);
|
);
|
||||||
@@ -550,17 +484,84 @@ fn offchain_http_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
let mut ext = ext.ext();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
call_in_wasm(
|
call_in_wasm(
|
||||||
"test_offchain_http",
|
"test_offchain_http",
|
||||||
&[0],
|
&[0],
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext.ext(),
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
true.encode(),
|
true.encode(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case(WasmExecutionMethod::Interpreted)]
|
||||||
|
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
||||||
|
#[should_panic(expected = "Allocator ran out of space")]
|
||||||
|
fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) {
|
||||||
|
let mut ext = TestExternalities::default();
|
||||||
|
|
||||||
|
crate::call_in_wasm::<HostFunctions>(
|
||||||
|
"test_exhaust_heap",
|
||||||
|
&[0],
|
||||||
|
wasm_method,
|
||||||
|
&mut ext.ext(),
|
||||||
|
&WASM_BINARY[..],
|
||||||
|
// `17` is the initial number of pages compiled into the binary.
|
||||||
|
17,
|
||||||
|
true,
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case(WasmExecutionMethod::Interpreted)]
|
||||||
|
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
||||||
|
fn returns_mutable_static(wasm_method: WasmExecutionMethod) {
|
||||||
|
let mut instance = crate::wasm_runtime::create_wasm_runtime_with_code(
|
||||||
|
wasm_method,
|
||||||
|
1024,
|
||||||
|
&WASM_BINARY[..],
|
||||||
|
HostFunctions::host_functions(),
|
||||||
|
true,
|
||||||
|
).expect("Creates instance");
|
||||||
|
|
||||||
|
let res = instance.call("returns_mutable_static", &[0]).unwrap();
|
||||||
|
assert_eq!(33, u64::decode(&mut &res[..]).unwrap());
|
||||||
|
|
||||||
|
// We expect that every invocation will need to return the initial
|
||||||
|
// value plus one. If the value increases more than that then it is
|
||||||
|
// a sign that the wasm runtime preserves the memory content.
|
||||||
|
let res = instance.call("returns_mutable_static", &[0]).unwrap();
|
||||||
|
assert_eq!(33, u64::decode(&mut &res[..]).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't restore the wasm instance properly, on a trap the stack pointer would not be
|
||||||
|
// returned to its initial value and thus the stack space is going to be leaked.
|
||||||
|
//
|
||||||
|
// See https://github.com/paritytech/substrate/issues/2967 for details
|
||||||
|
#[test_case(WasmExecutionMethod::Interpreted)]
|
||||||
|
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
||||||
|
fn restoration_of_globals(wasm_method: WasmExecutionMethod) {
|
||||||
|
// Allocate 32 pages (of 65536 bytes) which gives the runtime 2048KB of heap to operate on
|
||||||
|
// (plus some additional space unused from the initial pages requested by the wasm runtime
|
||||||
|
// module).
|
||||||
|
//
|
||||||
|
// The fixture performs 2 allocations of 768KB and this theoretically gives 1536KB, however, due
|
||||||
|
// to our allocator algorithm there are inefficiencies.
|
||||||
|
const REQUIRED_MEMORY_PAGES: u64 = 32;
|
||||||
|
|
||||||
|
let mut instance = crate::wasm_runtime::create_wasm_runtime_with_code(
|
||||||
|
wasm_method,
|
||||||
|
REQUIRED_MEMORY_PAGES,
|
||||||
|
&WASM_BINARY[..],
|
||||||
|
HostFunctions::host_functions(),
|
||||||
|
true,
|
||||||
|
).expect("Creates instance");
|
||||||
|
|
||||||
|
// On the first invocation we allocate approx. 768KB (75%) of stack and then trap.
|
||||||
|
let res = instance.call("allocates_huge_stack_array", &true.encode());
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
|
// On the second invocation we allocate yet another 768KB (75%) of stack
|
||||||
|
let res = instance.call("allocates_huge_stack_array", &false.encode());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ use super::{TestExternalities, call_in_wasm};
|
|||||||
use crate::WasmExecutionMethod;
|
use crate::WasmExecutionMethod;
|
||||||
|
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use sc_runtime_test::WASM_BINARY;
|
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
use wabt;
|
use wabt;
|
||||||
|
|
||||||
@@ -27,7 +26,6 @@ use wabt;
|
|||||||
fn sandbox_should_work(wasm_method: WasmExecutionMethod) {
|
fn sandbox_should_work(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let code = wabt::wat2wasm(r#"
|
let code = wabt::wat2wasm(r#"
|
||||||
(module
|
(module
|
||||||
@@ -56,8 +54,6 @@ fn sandbox_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
&code,
|
&code,
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
true.encode(),
|
true.encode(),
|
||||||
);
|
);
|
||||||
@@ -68,7 +64,6 @@ fn sandbox_should_work(wasm_method: WasmExecutionMethod) {
|
|||||||
fn sandbox_trap(wasm_method: WasmExecutionMethod) {
|
fn sandbox_trap(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let code = wabt::wat2wasm(r#"
|
let code = wabt::wat2wasm(r#"
|
||||||
(module
|
(module
|
||||||
@@ -86,47 +81,16 @@ fn sandbox_trap(wasm_method: WasmExecutionMethod) {
|
|||||||
&code,
|
&code,
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
vec![0],
|
vec![0],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(WasmExecutionMethod::Interpreted)]
|
|
||||||
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
|
||||||
#[should_panic(expected = "Allocator ran out of space")]
|
|
||||||
fn sandbox_should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) {
|
|
||||||
let mut ext = TestExternalities::default();
|
|
||||||
let mut ext = ext.ext();
|
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let code = wabt::wat2wasm(r#"
|
|
||||||
(module
|
|
||||||
(import "env" "assert" (func $assert (param i32)))
|
|
||||||
(func (export "call")
|
|
||||||
i32.const 0
|
|
||||||
call $assert
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"#).unwrap().encode();
|
|
||||||
|
|
||||||
call_in_wasm(
|
|
||||||
"test_exhaust_heap",
|
|
||||||
&code,
|
|
||||||
wasm_method,
|
|
||||||
&mut ext,
|
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test_case(WasmExecutionMethod::Interpreted)]
|
#[test_case(WasmExecutionMethod::Interpreted)]
|
||||||
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))]
|
||||||
fn start_called(wasm_method: WasmExecutionMethod) {
|
fn start_called(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let code = wabt::wat2wasm(r#"
|
let code = wabt::wat2wasm(r#"
|
||||||
(module
|
(module
|
||||||
@@ -161,8 +125,6 @@ fn start_called(wasm_method: WasmExecutionMethod) {
|
|||||||
&code,
|
&code,
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
true.encode(),
|
true.encode(),
|
||||||
);
|
);
|
||||||
@@ -173,7 +135,6 @@ fn start_called(wasm_method: WasmExecutionMethod) {
|
|||||||
fn invoke_args(wasm_method: WasmExecutionMethod) {
|
fn invoke_args(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let code = wabt::wat2wasm(r#"
|
let code = wabt::wat2wasm(r#"
|
||||||
(module
|
(module
|
||||||
@@ -204,8 +165,6 @@ fn invoke_args(wasm_method: WasmExecutionMethod) {
|
|||||||
&code,
|
&code,
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
true.encode(),
|
true.encode(),
|
||||||
);
|
);
|
||||||
@@ -216,7 +175,6 @@ fn invoke_args(wasm_method: WasmExecutionMethod) {
|
|||||||
fn return_val(wasm_method: WasmExecutionMethod) {
|
fn return_val(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let code = wabt::wat2wasm(r#"
|
let code = wabt::wat2wasm(r#"
|
||||||
(module
|
(module
|
||||||
@@ -235,8 +193,6 @@ fn return_val(wasm_method: WasmExecutionMethod) {
|
|||||||
&code,
|
&code,
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
true.encode(),
|
true.encode(),
|
||||||
);
|
);
|
||||||
@@ -247,7 +203,6 @@ fn return_val(wasm_method: WasmExecutionMethod) {
|
|||||||
fn unlinkable_module(wasm_method: WasmExecutionMethod) {
|
fn unlinkable_module(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let code = wabt::wat2wasm(r#"
|
let code = wabt::wat2wasm(r#"
|
||||||
(module
|
(module
|
||||||
@@ -264,8 +219,6 @@ fn unlinkable_module(wasm_method: WasmExecutionMethod) {
|
|||||||
&code,
|
&code,
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
1u8.encode(),
|
1u8.encode(),
|
||||||
);
|
);
|
||||||
@@ -276,7 +229,6 @@ fn unlinkable_module(wasm_method: WasmExecutionMethod) {
|
|||||||
fn corrupted_module(wasm_method: WasmExecutionMethod) {
|
fn corrupted_module(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
// Corrupted wasm file
|
// Corrupted wasm file
|
||||||
let code = vec![0u8, 0, 0, 0, 1, 0, 0, 0].encode();
|
let code = vec![0u8, 0, 0, 0, 1, 0, 0, 0].encode();
|
||||||
@@ -287,8 +239,6 @@ fn corrupted_module(wasm_method: WasmExecutionMethod) {
|
|||||||
&code,
|
&code,
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
1u8.encode(),
|
1u8.encode(),
|
||||||
);
|
);
|
||||||
@@ -299,7 +249,6 @@ fn corrupted_module(wasm_method: WasmExecutionMethod) {
|
|||||||
fn start_fn_ok(wasm_method: WasmExecutionMethod) {
|
fn start_fn_ok(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let code = wabt::wat2wasm(r#"
|
let code = wabt::wat2wasm(r#"
|
||||||
(module
|
(module
|
||||||
@@ -319,8 +268,6 @@ fn start_fn_ok(wasm_method: WasmExecutionMethod) {
|
|||||||
&code,
|
&code,
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
0u8.encode(),
|
0u8.encode(),
|
||||||
);
|
);
|
||||||
@@ -331,7 +278,6 @@ fn start_fn_ok(wasm_method: WasmExecutionMethod) {
|
|||||||
fn start_fn_traps(wasm_method: WasmExecutionMethod) {
|
fn start_fn_traps(wasm_method: WasmExecutionMethod) {
|
||||||
let mut ext = TestExternalities::default();
|
let mut ext = TestExternalities::default();
|
||||||
let mut ext = ext.ext();
|
let mut ext = ext.ext();
|
||||||
let test_code = WASM_BINARY;
|
|
||||||
|
|
||||||
let code = wabt::wat2wasm(r#"
|
let code = wabt::wat2wasm(r#"
|
||||||
(module
|
(module
|
||||||
@@ -352,8 +298,6 @@ fn start_fn_traps(wasm_method: WasmExecutionMethod) {
|
|||||||
&code,
|
&code,
|
||||||
wasm_method,
|
wasm_method,
|
||||||
&mut ext,
|
&mut ext,
|
||||||
&test_code[..],
|
|
||||||
8,
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
2u8.encode(),
|
2u8.encode(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use wasmi::{
|
|||||||
};
|
};
|
||||||
use codec::{Encode, Decode};
|
use codec::{Encode, Decode};
|
||||||
use sp_core::sandbox as sandbox_primitives;
|
use sp_core::sandbox as sandbox_primitives;
|
||||||
use log::{error, trace};
|
use log::{error, trace, debug};
|
||||||
use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule};
|
use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule};
|
||||||
use sp_wasm_interface::{
|
use sp_wasm_interface::{
|
||||||
FunctionContext, Pointer, WordSize, Sandbox, MemoryId, Result as WResult, Function,
|
FunctionContext, Pointer, WordSize, Sandbox, MemoryId, Result as WResult, Function,
|
||||||
@@ -271,18 +271,36 @@ impl<'a> Sandbox for FunctionExecutor<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Will be used on initialization of a module to resolve function and memory imports.
|
||||||
struct Resolver<'a> {
|
struct Resolver<'a> {
|
||||||
host_functions: &'a[&'static dyn Function],
|
/// All the hot functions that we export for the WASM blob.
|
||||||
|
host_functions: &'a [&'static dyn Function],
|
||||||
|
/// Should we allow missing function imports?
|
||||||
|
///
|
||||||
|
/// If `true`, we return a stub that will return an error when being called.
|
||||||
allow_missing_func_imports: bool,
|
allow_missing_func_imports: bool,
|
||||||
|
/// All the names of functions for that we did not provide a host function.
|
||||||
missing_functions: RefCell<Vec<String>>,
|
missing_functions: RefCell<Vec<String>>,
|
||||||
|
/// Will be used as initial and maximum size of the imported memory.
|
||||||
|
heap_pages: usize,
|
||||||
|
/// By default, runtimes should import memory and this is `Some(_)` after
|
||||||
|
/// reolving. However, to be backwards compatible, we also support memory
|
||||||
|
/// exported by the WASM blob (this will be `None` after resolving).
|
||||||
|
import_memory: RefCell<Option<MemoryRef>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Resolver<'a> {
|
impl<'a> Resolver<'a> {
|
||||||
fn new(host_functions: &'a[&'static dyn Function], allow_missing_func_imports: bool) -> Resolver<'a> {
|
fn new(
|
||||||
|
host_functions: &'a[&'static dyn Function],
|
||||||
|
allow_missing_func_imports: bool,
|
||||||
|
heap_pages: usize,
|
||||||
|
) -> Resolver<'a> {
|
||||||
Resolver {
|
Resolver {
|
||||||
host_functions,
|
host_functions,
|
||||||
allow_missing_func_imports,
|
allow_missing_func_imports,
|
||||||
missing_functions: RefCell::new(Vec::new()),
|
missing_functions: RefCell::new(Vec::new()),
|
||||||
|
heap_pages,
|
||||||
|
import_memory: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,6 +341,48 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_memory(
|
||||||
|
&self,
|
||||||
|
field_name: &str,
|
||||||
|
memory_type: &wasmi::MemoryDescriptor,
|
||||||
|
) -> Result<MemoryRef, wasmi::Error> {
|
||||||
|
if field_name == "memory" {
|
||||||
|
match &mut *self.import_memory.borrow_mut() {
|
||||||
|
Some(_) => Err(wasmi::Error::Instantiation(
|
||||||
|
"Memory can not be imported twice!".into(),
|
||||||
|
)),
|
||||||
|
memory_ref @ None => {
|
||||||
|
if memory_type
|
||||||
|
.maximum()
|
||||||
|
.map(|m| m.saturating_sub(memory_type.initial()))
|
||||||
|
.map(|m| self.heap_pages > m as usize)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
Err(wasmi::Error::Instantiation(format!(
|
||||||
|
"Heap pages ({}) is greater than imported memory maximum ({}).",
|
||||||
|
self.heap_pages,
|
||||||
|
memory_type
|
||||||
|
.maximum()
|
||||||
|
.map(|m| m.saturating_sub(memory_type.initial()))
|
||||||
|
.expect("Maximum is set, checked above; qed"),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
let memory = MemoryInstance::alloc(
|
||||||
|
Pages(memory_type.initial() as usize + self.heap_pages),
|
||||||
|
Some(Pages(memory_type.initial() as usize + self.heap_pages)),
|
||||||
|
)?;
|
||||||
|
*memory_ref = Some(memory.clone());
|
||||||
|
Ok(memory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(wasmi::Error::Instantiation(
|
||||||
|
format!("Unknown memory reference with name: {}", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> wasmi::Externals for FunctionExecutor<'a> {
|
impl<'a> wasmi::Externals for FunctionExecutor<'a> {
|
||||||
@@ -378,15 +438,14 @@ fn get_heap_base(module: &ModuleRef) -> Result<u32, Error> {
|
|||||||
/// Call a given method in the given wasm-module runtime.
|
/// Call a given method in the given wasm-module runtime.
|
||||||
fn call_in_wasm_module(
|
fn call_in_wasm_module(
|
||||||
module_instance: &ModuleRef,
|
module_instance: &ModuleRef,
|
||||||
|
memory: &MemoryRef,
|
||||||
method: &str,
|
method: &str,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
host_functions: &[&'static dyn Function],
|
host_functions: &[&'static dyn Function],
|
||||||
allow_missing_func_imports: bool,
|
allow_missing_func_imports: bool,
|
||||||
missing_functions: &Vec<String>,
|
missing_functions: &Vec<String>,
|
||||||
) -> Result<Vec<u8>, Error> {
|
) -> Result<Vec<u8>, Error> {
|
||||||
// extract a reference to a linear memory, optional reference to a table
|
// Initialize FunctionExecutor.
|
||||||
// and then initialize FunctionExecutor.
|
|
||||||
let memory = get_mem_instance(module_instance)?;
|
|
||||||
let table: Option<TableRef> = module_instance
|
let table: Option<TableRef> = module_instance
|
||||||
.export_by_name("__indirect_function_table")
|
.export_by_name("__indirect_function_table")
|
||||||
.and_then(|e| e.as_table().cloned());
|
.and_then(|e| e.as_table().cloned());
|
||||||
@@ -434,8 +493,8 @@ fn instantiate_module(
|
|||||||
module: &Module,
|
module: &Module,
|
||||||
host_functions: &[&'static dyn Function],
|
host_functions: &[&'static dyn Function],
|
||||||
allow_missing_func_imports: bool,
|
allow_missing_func_imports: bool,
|
||||||
) -> Result<(ModuleRef, Vec<String>), Error> {
|
) -> Result<(ModuleRef, Vec<String>, MemoryRef), Error> {
|
||||||
let resolver = Resolver::new(host_functions, allow_missing_func_imports);
|
let resolver = Resolver::new(host_functions, allow_missing_func_imports, heap_pages);
|
||||||
// start module instantiation. Don't run 'start' function yet.
|
// start module instantiation. Don't run 'start' function yet.
|
||||||
let intermediate_instance = ModuleInstance::new(
|
let intermediate_instance = ModuleInstance::new(
|
||||||
module,
|
module,
|
||||||
@@ -445,15 +504,33 @@ fn instantiate_module(
|
|||||||
// Verify that the module has the heap base global variable.
|
// Verify that the module has the heap base global variable.
|
||||||
let _ = get_heap_base(intermediate_instance.not_started_instance())?;
|
let _ = get_heap_base(intermediate_instance.not_started_instance())?;
|
||||||
|
|
||||||
// Extract a reference to a linear memory.
|
|
||||||
let memory = get_mem_instance(intermediate_instance.not_started_instance())?;
|
// Get the memory reference. Runtimes should import memory, but to be backwards
|
||||||
memory.grow(Pages(heap_pages)).map_err(|_| Error::Runtime)?;
|
// compatible we also support exported memory.
|
||||||
|
let memory = match resolver.import_memory.into_inner() {
|
||||||
|
Some(memory) => memory,
|
||||||
|
None => {
|
||||||
|
debug!(
|
||||||
|
target: "wasm-executor",
|
||||||
|
"WASM blob does not imports memory, falling back to exported memory",
|
||||||
|
);
|
||||||
|
|
||||||
|
let memory = get_mem_instance(intermediate_instance.not_started_instance())?;
|
||||||
|
memory.grow(Pages(heap_pages)).map_err(|_| Error::Runtime)?;
|
||||||
|
|
||||||
|
memory
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if intermediate_instance.has_start() {
|
if intermediate_instance.has_start() {
|
||||||
// Runtime is not allowed to have the `start` function.
|
// Runtime is not allowed to have the `start` function.
|
||||||
Err(Error::RuntimeHasStartFn)
|
Err(Error::RuntimeHasStartFn)
|
||||||
} else {
|
} else {
|
||||||
Ok((intermediate_instance.assert_no_start(), resolver.missing_functions.into_inner()))
|
Ok((
|
||||||
|
intermediate_instance.assert_no_start(),
|
||||||
|
resolver.missing_functions.into_inner(),
|
||||||
|
memory,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,14 +605,7 @@ impl StateSnapshot {
|
|||||||
/// the preserved memory and globals.
|
/// the preserved memory and globals.
|
||||||
///
|
///
|
||||||
/// Returns `Err` if applying the snapshot is failed.
|
/// Returns `Err` if applying the snapshot is failed.
|
||||||
fn apply(&self, instance: &ModuleRef) -> Result<(), WasmError> {
|
fn apply(&self, instance: &ModuleRef, memory: &MemoryRef) -> Result<(), WasmError> {
|
||||||
let memory = instance
|
|
||||||
.export_by_name("memory")
|
|
||||||
.ok_or(WasmError::ApplySnapshotFailed)?
|
|
||||||
.as_memory()
|
|
||||||
.cloned()
|
|
||||||
.ok_or(WasmError::ApplySnapshotFailed)?;
|
|
||||||
|
|
||||||
// First, erase the memory and copy the data segments into it.
|
// First, erase the memory and copy the data segments into it.
|
||||||
memory
|
memory
|
||||||
.erase()
|
.erase()
|
||||||
@@ -569,6 +639,8 @@ impl StateSnapshot {
|
|||||||
pub struct WasmiRuntime {
|
pub struct WasmiRuntime {
|
||||||
/// A wasm module instance.
|
/// A wasm module instance.
|
||||||
instance: ModuleRef,
|
instance: ModuleRef,
|
||||||
|
/// The memory instance of used by the wasm module.
|
||||||
|
memory: MemoryRef,
|
||||||
/// The snapshot of the instance's state taken just after the instantiation.
|
/// The snapshot of the instance's state taken just after the instantiation.
|
||||||
state_snapshot: StateSnapshot,
|
state_snapshot: StateSnapshot,
|
||||||
/// The host functions registered for this instance.
|
/// The host functions registered for this instance.
|
||||||
@@ -594,7 +666,7 @@ impl WasmRuntime for WasmiRuntime {
|
|||||||
method: &str,
|
method: &str,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
) -> Result<Vec<u8>, Error> {
|
) -> Result<Vec<u8>, Error> {
|
||||||
self.state_snapshot.apply(&self.instance)
|
self.state_snapshot.apply(&self.instance, &self.memory)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
// Snapshot restoration failed. This is pretty unexpected since this can happen
|
// Snapshot restoration failed. This is pretty unexpected since this can happen
|
||||||
// if some invariant is broken or if the system is under extreme memory pressure
|
// if some invariant is broken or if the system is under extreme memory pressure
|
||||||
@@ -604,6 +676,7 @@ impl WasmRuntime for WasmiRuntime {
|
|||||||
})?;
|
})?;
|
||||||
call_in_wasm_module(
|
call_in_wasm_module(
|
||||||
&self.instance,
|
&self.instance,
|
||||||
|
&self.memory,
|
||||||
method,
|
method,
|
||||||
data,
|
data,
|
||||||
&self.host_functions,
|
&self.host_functions,
|
||||||
@@ -628,7 +701,7 @@ pub fn create_instance(
|
|||||||
let data_segments = extract_data_segments(&code)?;
|
let data_segments = extract_data_segments(&code)?;
|
||||||
|
|
||||||
// Instantiate this module.
|
// Instantiate this module.
|
||||||
let (instance, missing_functions) = instantiate_module(
|
let (instance, missing_functions, memory) = instantiate_module(
|
||||||
heap_pages as usize,
|
heap_pages as usize,
|
||||||
&module,
|
&module,
|
||||||
&host_functions,
|
&host_functions,
|
||||||
@@ -646,6 +719,7 @@ pub fn create_instance(
|
|||||||
|
|
||||||
Ok(WasmiRuntime {
|
Ok(WasmiRuntime {
|
||||||
instance,
|
instance,
|
||||||
|
memory,
|
||||||
state_snapshot,
|
state_snapshot,
|
||||||
host_functions,
|
host_functions,
|
||||||
allow_missing_func_imports,
|
allow_missing_func_imports,
|
||||||
|
|||||||
@@ -32,42 +32,38 @@ use sc_executor_common::{
|
|||||||
use sp_wasm_interface::{Pointer, WordSize, Function};
|
use sp_wasm_interface::{Pointer, WordSize, Function};
|
||||||
use sp_runtime_interface::unpack_ptr_and_len;
|
use sp_runtime_interface::unpack_ptr_and_len;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::{cell::RefCell, collections::HashMap, convert::TryFrom, rc::Rc};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use cranelift_codegen::ir;
|
use cranelift_codegen::ir;
|
||||||
use cranelift_codegen::isa::TargetIsa;
|
use cranelift_codegen::isa::TargetIsa;
|
||||||
use cranelift_entity::{EntityRef, PrimaryMap};
|
use cranelift_entity::{EntityRef, PrimaryMap};
|
||||||
use cranelift_frontend::FunctionBuilderContext;
|
use cranelift_frontend::FunctionBuilderContext;
|
||||||
use cranelift_wasm::DefinedFuncIndex;
|
use cranelift_wasm::{DefinedFuncIndex, MemoryIndex};
|
||||||
use wasmtime_environ::{Module, translate_signature};
|
use wasmtime_environ::{Module, translate_signature};
|
||||||
use wasmtime_jit::{
|
use wasmtime_jit::{
|
||||||
ActionOutcome, CodeMemory, CompilationStrategy, CompiledModule, Compiler, Context, RuntimeValue,
|
ActionOutcome, CodeMemory, CompilationStrategy, CompiledModule, Compiler, Context, RuntimeValue,
|
||||||
};
|
};
|
||||||
use wasmtime_runtime::{Export, Imports, InstanceHandle, VMFunctionBody};
|
use wasmtime_runtime::{Export, Imports, InstanceHandle, VMFunctionBody};
|
||||||
|
|
||||||
|
/// TODO: We should remove this in https://github.com/paritytech/substrate/pull/4686
|
||||||
|
/// Currently there is no way to extract this with wasmtime.
|
||||||
|
const INITIAL_HEAP_PAGES: u32 = 17;
|
||||||
|
|
||||||
/// A `WasmRuntime` implementation using the Wasmtime JIT to compile the runtime module to native
|
/// A `WasmRuntime` implementation using the Wasmtime JIT to compile the runtime module to native
|
||||||
/// and execute the compiled code.
|
/// and execute the compiled code.
|
||||||
pub struct WasmtimeRuntime {
|
pub struct WasmtimeRuntime {
|
||||||
module: CompiledModule,
|
module: CompiledModule,
|
||||||
context: Context,
|
context: Context,
|
||||||
max_heap_pages: Option<u32>,
|
|
||||||
heap_pages: u32,
|
heap_pages: u32,
|
||||||
/// The host functions registered for this instance.
|
/// The host functions registered for this instance.
|
||||||
host_functions: Vec<&'static dyn Function>,
|
host_functions: Vec<&'static dyn Function>,
|
||||||
|
/// The index of the memory in the module.
|
||||||
|
memory_index: MemoryIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WasmRuntime for WasmtimeRuntime {
|
impl WasmRuntime for WasmtimeRuntime {
|
||||||
fn update_heap_pages(&mut self, heap_pages: u64) -> bool {
|
fn update_heap_pages(&mut self, heap_pages: u64) -> bool {
|
||||||
match heap_pages_valid(heap_pages, self.max_heap_pages) {
|
self.heap_pages as u64 == heap_pages
|
||||||
Some(heap_pages) => {
|
|
||||||
self.heap_pages = heap_pages;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn host_functions(&self) -> &[&'static dyn Function] {
|
fn host_functions(&self) -> &[&'static dyn Function] {
|
||||||
@@ -80,6 +76,7 @@ impl WasmRuntime for WasmtimeRuntime {
|
|||||||
&mut self.module,
|
&mut self.module,
|
||||||
method,
|
method,
|
||||||
data,
|
data,
|
||||||
|
self.memory_index,
|
||||||
self.heap_pages,
|
self.heap_pages,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -93,31 +90,42 @@ pub fn create_instance(
|
|||||||
host_functions: Vec<&'static dyn Function>,
|
host_functions: Vec<&'static dyn Function>,
|
||||||
allow_missing_func_imports: bool,
|
allow_missing_func_imports: bool,
|
||||||
) -> std::result::Result<WasmtimeRuntime, WasmError> {
|
) -> std::result::Result<WasmtimeRuntime, WasmError> {
|
||||||
let (compiled_module, context) = create_compiled_unit(code, &host_functions, allow_missing_func_imports)?;
|
let heap_pages = u32::try_from(heap_pages)
|
||||||
|
.map_err(|e|
|
||||||
|
WasmError::Other(format!("Heap pages can not be converted into `u32`: {:?}", e))
|
||||||
|
)?;
|
||||||
|
|
||||||
// Inspect the module for the min and max memory sizes.
|
let (compiled_module, context, memory_index) = create_compiled_unit(
|
||||||
let (min_memory_size, max_memory_size) = {
|
code,
|
||||||
let module = compiled_module.module_ref();
|
&host_functions,
|
||||||
let memory_index = match module.exports.get("memory") {
|
heap_pages,
|
||||||
Some(wasmtime_environ::Export::Memory(memory_index)) => *memory_index,
|
allow_missing_func_imports,
|
||||||
_ => return Err(WasmError::InvalidMemory),
|
)?;
|
||||||
|
|
||||||
|
let module = compiled_module.module_ref();
|
||||||
|
if !module.is_imported_memory(memory_index) {
|
||||||
|
// Inspect the module for the min and max memory sizes.
|
||||||
|
let (min_memory_size, max_memory_size) = {
|
||||||
|
let memory_plan = module.memory_plans
|
||||||
|
.get(memory_index)
|
||||||
|
.ok_or_else(|| WasmError::InvalidMemory)?;
|
||||||
|
(memory_plan.memory.minimum, memory_plan.memory.maximum)
|
||||||
};
|
};
|
||||||
let memory_plan = module.memory_plans.get(memory_index)
|
|
||||||
.expect("memory_index is retrieved from the module's exports map; qed");
|
|
||||||
(memory_plan.memory.minimum, memory_plan.memory.maximum)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check that heap_pages is within the allowed range.
|
// Check that heap_pages is within the allowed range.
|
||||||
let max_heap_pages = max_memory_size.map(|max| max.saturating_sub(min_memory_size));
|
let max_heap_pages = max_memory_size.map(|max| max.saturating_sub(min_memory_size));
|
||||||
let heap_pages = heap_pages_valid(heap_pages, max_heap_pages)
|
|
||||||
.ok_or_else(|| WasmError::InvalidHeapPages)?;
|
if max_heap_pages.map(|m| heap_pages > m).unwrap_or(false) {
|
||||||
|
return Err(WasmError::InvalidHeapPages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(WasmtimeRuntime {
|
Ok(WasmtimeRuntime {
|
||||||
module: compiled_module,
|
module: compiled_module,
|
||||||
context,
|
context,
|
||||||
max_heap_pages,
|
|
||||||
heap_pages,
|
heap_pages,
|
||||||
host_functions,
|
host_functions,
|
||||||
|
memory_index,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,8 +217,9 @@ fn scan_missing_functions(
|
|||||||
fn create_compiled_unit(
|
fn create_compiled_unit(
|
||||||
code: &[u8],
|
code: &[u8],
|
||||||
host_functions: &[&'static dyn Function],
|
host_functions: &[&'static dyn Function],
|
||||||
|
heap_pages: u32,
|
||||||
allow_missing_func_imports: bool,
|
allow_missing_func_imports: bool,
|
||||||
) -> std::result::Result<(CompiledModule, Context), WasmError> {
|
) -> std::result::Result<(CompiledModule, Context, MemoryIndex), WasmError> {
|
||||||
let compilation_strategy = CompilationStrategy::Cranelift;
|
let compilation_strategy = CompilationStrategy::Cranelift;
|
||||||
|
|
||||||
let compiler = new_compiler(compilation_strategy)?;
|
let compiler = new_compiler(compilation_strategy)?;
|
||||||
@@ -231,16 +240,32 @@ fn create_compiled_unit(
|
|||||||
MissingFunctionStubs::new()
|
MissingFunctionStubs::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let env_missing_functions = missing_functions_stubs.stubs.remove("env").unwrap_or_else(|| Vec::new());
|
let env_missing_functions = missing_functions_stubs.stubs
|
||||||
context.name_instance(
|
.remove("env")
|
||||||
"env".to_owned(),
|
.unwrap_or_else(|| Vec::new());
|
||||||
instantiate_env_module(global_exports, compiler, host_functions, env_missing_functions)?,
|
|
||||||
);
|
let (module, memory_index) = instantiate_env_module(
|
||||||
|
global_exports,
|
||||||
|
compiler,
|
||||||
|
host_functions,
|
||||||
|
heap_pages,
|
||||||
|
env_missing_functions,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
context.name_instance("env".to_owned(), module);
|
||||||
|
|
||||||
for (module, missing_functions_stubs) in missing_functions_stubs.stubs {
|
for (module, missing_functions_stubs) in missing_functions_stubs.stubs {
|
||||||
let compiler = new_compiler(compilation_strategy)?;
|
let compiler = new_compiler(compilation_strategy)?;
|
||||||
let global_exports = context.get_global_exports();
|
let global_exports = context.get_global_exports();
|
||||||
let instance = instantiate_env_module(global_exports, compiler, &[], missing_functions_stubs)?;
|
let instance = instantiate_env_module(
|
||||||
|
global_exports,
|
||||||
|
compiler,
|
||||||
|
&[],
|
||||||
|
heap_pages,
|
||||||
|
missing_functions_stubs,
|
||||||
|
false,
|
||||||
|
)?.0;
|
||||||
context.name_instance(module, instance);
|
context.name_instance(module, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +273,7 @@ fn create_compiled_unit(
|
|||||||
let module = context.compile_module(&code)
|
let module = context.compile_module(&code)
|
||||||
.map_err(|e| WasmError::Other(format!("module compile error: {}", e)))?;
|
.map_err(|e| WasmError::Other(format!("module compile error: {}", e)))?;
|
||||||
|
|
||||||
Ok((module, context))
|
Ok((module, context, memory_index.expect("Memory is added on request; qed")))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a function inside a precompiled Wasm module.
|
/// Call a function inside a precompiled Wasm module.
|
||||||
@@ -257,22 +282,21 @@ fn call_method(
|
|||||||
module: &mut CompiledModule,
|
module: &mut CompiledModule,
|
||||||
method: &str,
|
method: &str,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
|
memory_index: MemoryIndex,
|
||||||
heap_pages: u32,
|
heap_pages: u32,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
|
let is_imported_memory = module.module().is_imported_memory(memory_index);
|
||||||
// Old exports get clobbered in `InstanceHandle::new` if we don't explicitly remove them first.
|
// Old exports get clobbered in `InstanceHandle::new` if we don't explicitly remove them first.
|
||||||
//
|
//
|
||||||
// The global exports mechanism is temporary in Wasmtime and expected to be removed.
|
// The global exports mechanism is temporary in Wasmtime and expected to be removed.
|
||||||
// https://github.com/CraneStation/wasmtime/issues/332
|
// https://github.com/CraneStation/wasmtime/issues/332
|
||||||
clear_globals(&mut *context.get_global_exports().borrow_mut());
|
clear_globals(&mut *context.get_global_exports().borrow_mut(), is_imported_memory);
|
||||||
|
|
||||||
let mut instance = module.instantiate()
|
let mut instance = module.instantiate().map_err(|e| Error::Other(e.to_string()))?;
|
||||||
.map_err(|e| Error::Other(e.to_string()))?;
|
|
||||||
|
|
||||||
// Ideally there would be a way to set the heap pages during instantiation rather than
|
if !is_imported_memory {
|
||||||
// growing the memory after the fact. Currently this may require an additional mmap and copy.
|
grow_memory(&mut instance, heap_pages)?;
|
||||||
// However, the wasmtime API doesn't support modifying the size of memory on instantiation
|
}
|
||||||
// at this time.
|
|
||||||
grow_memory(&mut instance, heap_pages)?;
|
|
||||||
|
|
||||||
// Initialize the function executor state.
|
// Initialize the function executor state.
|
||||||
let heap_base = get_heap_base(&instance)?;
|
let heap_base = get_heap_base(&instance)?;
|
||||||
@@ -280,7 +304,7 @@ fn call_method(
|
|||||||
reset_env_state_and_take_trap(context, Some(executor_state))?;
|
reset_env_state_and_take_trap(context, Some(executor_state))?;
|
||||||
|
|
||||||
// Write the input data into guest memory.
|
// Write the input data into guest memory.
|
||||||
let (data_ptr, data_len) = inject_input_data(context, &mut instance, data)?;
|
let (data_ptr, data_len) = inject_input_data(context, &mut instance, data, memory_index)?;
|
||||||
let args = [RuntimeValue::I32(u32::from(data_ptr) as i32), RuntimeValue::I32(data_len as i32)];
|
let args = [RuntimeValue::I32(u32::from(data_ptr) as i32), RuntimeValue::I32(data_len as i32)];
|
||||||
|
|
||||||
// Invoke the function in the runtime.
|
// Invoke the function in the runtime.
|
||||||
@@ -300,7 +324,7 @@ fn call_method(
|
|||||||
|
|
||||||
// Read the output data from guest memory.
|
// Read the output data from guest memory.
|
||||||
let mut output = vec![0; output_len as usize];
|
let mut output = vec![0; output_len as usize];
|
||||||
let memory = get_memory_mut(&mut instance)?;
|
let memory = get_memory_mut(&mut instance, memory_index)?;
|
||||||
read_memory_into(memory, Pointer::new(output_ptr), &mut output)?;
|
read_memory_into(memory, Pointer::new(output_ptr), &mut output)?;
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
@@ -310,9 +334,10 @@ fn instantiate_env_module(
|
|||||||
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
|
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
|
||||||
compiler: Compiler,
|
compiler: Compiler,
|
||||||
host_functions: &[&'static dyn Function],
|
host_functions: &[&'static dyn Function],
|
||||||
|
heap_pages: u32,
|
||||||
missing_functions_stubs: Vec<MissingFunction>,
|
missing_functions_stubs: Vec<MissingFunction>,
|
||||||
) -> std::result::Result<InstanceHandle, WasmError>
|
add_memory: bool,
|
||||||
{
|
) -> std::result::Result<(InstanceHandle, Option<MemoryIndex>), WasmError> {
|
||||||
let isa = target_isa()?;
|
let isa = target_isa()?;
|
||||||
let pointer_type = isa.pointer_type();
|
let pointer_type = isa.pointer_type();
|
||||||
let call_conv = isa.default_call_conv();
|
let call_conv = isa.default_call_conv();
|
||||||
@@ -325,7 +350,7 @@ fn instantiate_env_module(
|
|||||||
for function in host_functions {
|
for function in host_functions {
|
||||||
let sig = translate_signature(
|
let sig = translate_signature(
|
||||||
cranelift_ir_signature(function.signature(), &call_conv),
|
cranelift_ir_signature(function.signature(), &call_conv),
|
||||||
pointer_type
|
pointer_type,
|
||||||
);
|
);
|
||||||
let sig_id = module.signatures.push(sig.clone());
|
let sig_id = module.signatures.push(sig.clone());
|
||||||
let func_id = module.functions.push(sig_id);
|
let func_id = module.functions.push(sig_id);
|
||||||
@@ -365,6 +390,22 @@ fn instantiate_env_module(
|
|||||||
|
|
||||||
code_memory.publish();
|
code_memory.publish();
|
||||||
|
|
||||||
|
let memory_id = if add_memory {
|
||||||
|
let memory = cranelift_wasm::Memory {
|
||||||
|
minimum: heap_pages + INITIAL_HEAP_PAGES,
|
||||||
|
maximum: Some(heap_pages + INITIAL_HEAP_PAGES),
|
||||||
|
shared: false,
|
||||||
|
};
|
||||||
|
let memory_plan = wasmtime_environ::MemoryPlan::for_memory(memory, &Default::default());
|
||||||
|
|
||||||
|
let memory_id = module.memory_plans.push(memory_plan);
|
||||||
|
module.exports.insert("memory".into(), wasmtime_environ::Export::Memory(memory_id));
|
||||||
|
|
||||||
|
Some(memory_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let imports = Imports::none();
|
let imports = Imports::none();
|
||||||
let data_initializers = Vec::new();
|
let data_initializers = Vec::new();
|
||||||
let signatures = PrimaryMap::new();
|
let signatures = PrimaryMap::new();
|
||||||
@@ -380,7 +421,10 @@ fn instantiate_env_module(
|
|||||||
None,
|
None,
|
||||||
Box::new(env_state),
|
Box::new(env_state),
|
||||||
);
|
);
|
||||||
result.map_err(|e| WasmError::Other(format!("cannot instantiate env: {}", e)))
|
|
||||||
|
result
|
||||||
|
.map_err(|e| WasmError::Other(format!("cannot instantiate env: {}", e)))
|
||||||
|
.map(|r| (r, memory_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a new TargetIsa for the host machine.
|
/// Build a new TargetIsa for the host machine.
|
||||||
@@ -396,8 +440,11 @@ fn new_compiler(strategy: CompilationStrategy) -> std::result::Result<Compiler,
|
|||||||
Ok(Compiler::new(isa, strategy))
|
Ok(Compiler::new(isa, strategy))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_globals(global_exports: &mut HashMap<String, Option<Export>>) {
|
fn clear_globals(global_exports: &mut HashMap<String, Option<Export>>, is_imported_memory: bool) {
|
||||||
global_exports.remove("memory");
|
// When memory is imported, we can not delete the global export.
|
||||||
|
if !is_imported_memory {
|
||||||
|
global_exports.remove("memory");
|
||||||
|
}
|
||||||
global_exports.remove("__heap_base");
|
global_exports.remove("__heap_base");
|
||||||
global_exports.remove("__indirect_function_table");
|
global_exports.remove("__indirect_function_table");
|
||||||
}
|
}
|
||||||
@@ -441,13 +488,14 @@ fn inject_input_data(
|
|||||||
context: &mut Context,
|
context: &mut Context,
|
||||||
instance: &mut InstanceHandle,
|
instance: &mut InstanceHandle,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
|
memory_index: MemoryIndex,
|
||||||
) -> Result<(Pointer<u8>, WordSize)> {
|
) -> Result<(Pointer<u8>, WordSize)> {
|
||||||
let env_state = get_env_state(context)?;
|
let env_state = get_env_state(context)?;
|
||||||
let executor_state = env_state.executor_state
|
let executor_state = env_state.executor_state
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or_else(|| "cannot get \"env\" module executor state")?;
|
.ok_or_else(|| "cannot get \"env\" module executor state")?;
|
||||||
|
|
||||||
let memory = get_memory_mut(instance)?;
|
let memory = get_memory_mut(instance, memory_index)?;
|
||||||
|
|
||||||
let data_len = data.len() as WordSize;
|
let data_len = data.len() as WordSize;
|
||||||
let data_ptr = executor_state.heap().allocate(memory, data_len)?;
|
let data_ptr = executor_state.heap().allocate(memory, data_len)?;
|
||||||
@@ -455,12 +503,12 @@ fn inject_input_data(
|
|||||||
Ok((data_ptr, data_len))
|
Ok((data_ptr, data_len))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_memory_mut(instance: &mut InstanceHandle) -> Result<&mut [u8]> {
|
fn get_memory_mut(instance: &mut InstanceHandle, memory_index: MemoryIndex) -> Result<&mut [u8]> {
|
||||||
match instance.lookup("memory") {
|
match instance.lookup_by_declaration(&wasmtime_environ::Export::Memory(memory_index)) {
|
||||||
// This is safe to wrap in an unsafe block as:
|
// This is safe to wrap in an unsafe block as:
|
||||||
// - The definition pointer is returned by a lookup on a valid instance and thus points to
|
// - The definition pointer is returned by a lookup on a valid instance and thus points to
|
||||||
// a valid memory definition
|
// a valid memory definition
|
||||||
Some(Export::Memory { definition, vmctx: _, memory: _ }) => unsafe {
|
Export::Memory { definition, vmctx: _, memory: _ } => unsafe {
|
||||||
Ok(std::slice::from_raw_parts_mut(
|
Ok(std::slice::from_raw_parts_mut(
|
||||||
(*definition).base,
|
(*definition).base,
|
||||||
(*definition).current_length,
|
(*definition).current_length,
|
||||||
@@ -484,17 +532,3 @@ fn get_heap_base(instance: &InstanceHandle) -> Result<u32> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks whether the heap_pages parameter is within the valid range and converts it to a u32.
|
|
||||||
/// Returns None if heaps_pages in not in range.
|
|
||||||
fn heap_pages_valid(heap_pages: u64, max_heap_pages: Option<u32>)
|
|
||||||
-> Option<u32>
|
|
||||||
{
|
|
||||||
let heap_pages = u32::try_from(heap_pages).ok()?;
|
|
||||||
if let Some(max_heap_pages) = max_heap_pages {
|
|
||||||
if heap_pages > max_heap_pages {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(heap_pages)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,17 +14,13 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource};
|
use wasm_builder_runner::WasmBuilder;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
build_current_project_with_rustflags(
|
WasmBuilder::new()
|
||||||
"wasm_binary.rs",
|
.with_current_project()
|
||||||
WasmBuilderSource::CratesOrPath {
|
.with_wasm_builder_from_crates_or_path("1.0.9", "../../../utils/wasm-builder")
|
||||||
path: "../../../utils/wasm-builder",
|
.export_heap_base()
|
||||||
version: "1.0.9",
|
.import_memory()
|
||||||
},
|
.build()
|
||||||
// This instructs LLD to export __heap_base as a global variable, which is used by the
|
|
||||||
// external memory allocator.
|
|
||||||
"-Clink-arg=--export=__heap_base",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,6 @@ impl<Block: BlockT, Executor, Backend, G: GenesisInit> TestClientBuilder<Block,
|
|||||||
Executor: sc_client::CallExecutor<Block> + 'static,
|
Executor: sc_client::CallExecutor<Block> + 'static,
|
||||||
Backend: sc_client_api::backend::Backend<Block>,
|
Backend: sc_client_api::backend::Backend<Block>,
|
||||||
{
|
{
|
||||||
|
|
||||||
let storage = {
|
let storage = {
|
||||||
let mut storage = self.genesis_init.genesis_storage();
|
let mut storage = self.genesis_init.genesis_storage();
|
||||||
|
|
||||||
|
|||||||
@@ -14,22 +14,17 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use wasm_builder_runner::{build_current_project_with_rustflags, WasmBuilderSource};
|
use wasm_builder_runner::WasmBuilder;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
build_current_project_with_rustflags(
|
WasmBuilder::new()
|
||||||
"wasm_binary.rs",
|
.with_current_project()
|
||||||
WasmBuilderSource::CratesOrPath {
|
.with_wasm_builder_from_crates_or_path("1.0.9", "../../utils/wasm-builder")
|
||||||
path: "../../utils/wasm-builder",
|
.export_heap_base()
|
||||||
version: "1.0.9",
|
|
||||||
},
|
|
||||||
// Note that we set the stack-size to 1MB explicitly even though it is set
|
// Note that we set the stack-size to 1MB explicitly even though it is set
|
||||||
// to this value by default. This is because some of our tests (`restoration_of_globals`)
|
// to this value by default. This is because some of our tests (`restoration_of_globals`)
|
||||||
// depend on the stack-size.
|
// depend on the stack-size.
|
||||||
//
|
.append_to_rust_flags("-Clink-arg=-zstack-size=1048576")
|
||||||
// The --export=__heap_base instructs LLD to export __heap_base as a global variable, which
|
.import_memory()
|
||||||
// is used by the external memory allocator.
|
.build()
|
||||||
"-Clink-arg=-zstack-size=1048576 \
|
|
||||||
-Clink-arg=--export=__heap_base",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,8 +255,6 @@ cfg_if! {
|
|||||||
fn use_trie() -> u64;
|
fn use_trie() -> u64;
|
||||||
fn benchmark_indirect_call() -> u64;
|
fn benchmark_indirect_call() -> u64;
|
||||||
fn benchmark_direct_call() -> u64;
|
fn benchmark_direct_call() -> u64;
|
||||||
fn returns_mutable_static() -> u64;
|
|
||||||
fn allocates_huge_stack_array(trap: bool) -> Vec<u8>;
|
|
||||||
fn vec_with_capacity(size: u32) -> Vec<u8>;
|
fn vec_with_capacity(size: u32) -> Vec<u8>;
|
||||||
/// Returns the initialized block number.
|
/// Returns the initialized block number.
|
||||||
fn get_block_number() -> u64;
|
fn get_block_number() -> u64;
|
||||||
@@ -299,8 +297,6 @@ cfg_if! {
|
|||||||
fn use_trie() -> u64;
|
fn use_trie() -> u64;
|
||||||
fn benchmark_indirect_call() -> u64;
|
fn benchmark_indirect_call() -> u64;
|
||||||
fn benchmark_direct_call() -> u64;
|
fn benchmark_direct_call() -> u64;
|
||||||
fn returns_mutable_static() -> u64;
|
|
||||||
fn allocates_huge_stack_array(trap: bool) -> Vec<u8>;
|
|
||||||
fn vec_with_capacity(size: u32) -> Vec<u8>;
|
fn vec_with_capacity(size: u32) -> Vec<u8>;
|
||||||
/// Returns the initialized block number.
|
/// Returns the initialized block number.
|
||||||
fn get_block_number() -> u64;
|
fn get_block_number() -> u64;
|
||||||
@@ -448,11 +444,6 @@ impl_opaque_keys! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
/// Mutable static variables should be always observed to have
|
|
||||||
/// the initialized value at the start of a runtime call.
|
|
||||||
static mut MUTABLE_STATIC: u64 = 32;
|
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "std")] {
|
if #[cfg(feature = "std")] {
|
||||||
impl_runtime_apis! {
|
impl_runtime_apis! {
|
||||||
@@ -558,14 +549,6 @@ cfg_if! {
|
|||||||
(0..1000).fold(0, |p, i| p + benchmark_add_one(i))
|
(0..1000).fold(0, |p, i| p + benchmark_add_one(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn returns_mutable_static() -> u64 {
|
|
||||||
unimplemented!("is not expected to be invoked from non-wasm builds");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allocates_huge_stack_array(_trap: bool) -> Vec<u8> {
|
|
||||||
unimplemented!("is not expected to be invoked from non-wasm builds");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vec_with_capacity(_size: u32) -> Vec<u8> {
|
fn vec_with_capacity(_size: u32) -> Vec<u8> {
|
||||||
unimplemented!("is not expected to be invoked from non-wasm builds");
|
unimplemented!("is not expected to be invoked from non-wasm builds");
|
||||||
}
|
}
|
||||||
@@ -753,41 +736,6 @@ cfg_if! {
|
|||||||
(0..10000).fold(0, |p, i| p + benchmark_add_one(i))
|
(0..10000).fold(0, |p, i| p + benchmark_add_one(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn returns_mutable_static() -> u64 {
|
|
||||||
unsafe {
|
|
||||||
MUTABLE_STATIC += 1;
|
|
||||||
MUTABLE_STATIC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allocates_huge_stack_array(trap: bool) -> Vec<u8> {
|
|
||||||
// Allocate a stack frame that is approx. 75% of the stack (assuming it is 1MB).
|
|
||||||
// This will just decrease (stacks in wasm32-u-u grow downwards) the stack
|
|
||||||
// pointer. This won't trap on the current compilers.
|
|
||||||
let mut data = [0u8; 1024 * 768];
|
|
||||||
|
|
||||||
// Then make sure we actually write something to it.
|
|
||||||
//
|
|
||||||
// If:
|
|
||||||
// 1. the stack area is placed at the beginning of the linear memory space, and
|
|
||||||
// 2. the stack pointer points to out-of-bounds area, and
|
|
||||||
// 3. a write is performed around the current stack pointer.
|
|
||||||
//
|
|
||||||
// then a trap should happen.
|
|
||||||
//
|
|
||||||
for (i, v) in data.iter_mut().enumerate() {
|
|
||||||
*v = i as u8; // deliberate truncation
|
|
||||||
}
|
|
||||||
|
|
||||||
if trap {
|
|
||||||
// There is a small chance of this to be pulled up in theory. In practice
|
|
||||||
// the probability of that is rather low.
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
|
|
||||||
data.to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vec_with_capacity(size: u32) -> Vec<u8> {
|
fn vec_with_capacity(size: u32) -> Vec<u8> {
|
||||||
Vec::with_capacity(size as usize)
|
Vec::with_capacity(size as usize)
|
||||||
}
|
}
|
||||||
@@ -970,54 +918,6 @@ mod tests {
|
|||||||
use sp_state_machine::ExecutionStrategy;
|
use sp_state_machine::ExecutionStrategy;
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn returns_mutable_static() {
|
|
||||||
let client = TestClientBuilder::new()
|
|
||||||
.set_execution_strategy(ExecutionStrategy::AlwaysWasm)
|
|
||||||
.build();
|
|
||||||
let runtime_api = client.runtime_api();
|
|
||||||
let block_id = BlockId::Number(client.chain_info().best_number);
|
|
||||||
|
|
||||||
let ret = runtime_api.returns_mutable_static(&block_id).unwrap();
|
|
||||||
assert_eq!(ret, 33);
|
|
||||||
|
|
||||||
// We expect that every invocation will need to return the initial
|
|
||||||
// value plus one. If the value increases more than that then it is
|
|
||||||
// a sign that the wasm runtime preserves the memory content.
|
|
||||||
let ret = runtime_api.returns_mutable_static(&block_id).unwrap();
|
|
||||||
assert_eq!(ret, 33);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we didn't restore the wasm instance properly, on a trap the stack pointer would not be
|
|
||||||
// returned to its initial value and thus the stack space is going to be leaked.
|
|
||||||
//
|
|
||||||
// See https://github.com/paritytech/substrate/issues/2967 for details
|
|
||||||
#[test]
|
|
||||||
fn restoration_of_globals() {
|
|
||||||
// Allocate 32 pages (of 65536 bytes) which gives the runtime 2048KB of heap to operate on
|
|
||||||
// (plus some additional space unused from the initial pages requested by the wasm runtime
|
|
||||||
// module).
|
|
||||||
//
|
|
||||||
// The fixture performs 2 allocations of 768KB and this theoretically gives 1536KB, however, due
|
|
||||||
// to our allocator algorithm there are inefficiencies.
|
|
||||||
const REQUIRED_MEMORY_PAGES: u64 = 32;
|
|
||||||
|
|
||||||
let client = TestClientBuilder::new()
|
|
||||||
.set_execution_strategy(ExecutionStrategy::AlwaysWasm)
|
|
||||||
.set_heap_pages(REQUIRED_MEMORY_PAGES)
|
|
||||||
.build();
|
|
||||||
let runtime_api = client.runtime_api();
|
|
||||||
let block_id = BlockId::Number(client.chain_info().best_number);
|
|
||||||
|
|
||||||
// On the first invocation we allocate approx. 768KB (75%) of stack and then trap.
|
|
||||||
let ret = runtime_api.allocates_huge_stack_array(&block_id, true);
|
|
||||||
assert!(ret.is_err());
|
|
||||||
|
|
||||||
// On the second invocation we allocate yet another 768KB (75%) of stack
|
|
||||||
let ret = runtime_api.allocates_huge_stack_array(&block_id, false);
|
|
||||||
assert!(ret.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn heap_pages_is_respected() {
|
fn heap_pages_is_respected() {
|
||||||
// This tests that the on-chain HEAP_PAGES parameter is respected.
|
// This tests that the on-chain HEAP_PAGES parameter is respected.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "substrate-wasm-builder-runner"
|
name = "substrate-wasm-builder-runner"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
description = "Runner for substrate-wasm-builder"
|
description = "Runner for substrate-wasm-builder"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|||||||
@@ -25,7 +25,10 @@
|
|||||||
//!
|
//!
|
||||||
//! For more information see <https://crates.io/substrate-wasm-builder>
|
//! For more information see <https://crates.io/substrate-wasm-builder>
|
||||||
|
|
||||||
use std::{env, process::{Command, self}, fs, path::{PathBuf, Path}};
|
use std::{
|
||||||
|
env, process::{Command, self}, fs, path::{PathBuf, Path}, hash::{Hash, Hasher},
|
||||||
|
collections::hash_map::DefaultHasher,
|
||||||
|
};
|
||||||
|
|
||||||
/// Environment variable that tells us to skip building the WASM binary.
|
/// Environment variable that tells us to skip building the WASM binary.
|
||||||
const SKIP_BUILD_ENV: &str = "SKIP_WASM_BUILD";
|
const SKIP_BUILD_ENV: &str = "SKIP_WASM_BUILD";
|
||||||
@@ -47,6 +50,225 @@ fn replace_back_slashes<T: ToString>(path: T) -> String {
|
|||||||
path.to_string().replace("\\", "/")
|
path.to_string().replace("\\", "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the manifest dir from the `CARGO_MANIFEST_DIR` env.
|
||||||
|
fn get_manifest_dir() -> PathBuf {
|
||||||
|
env::var("CARGO_MANIFEST_DIR")
|
||||||
|
.expect("`CARGO_MANIFEST_DIR` is always set for `build.rs` files; qed")
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// First step of the [`WasmBuilder`] to select the project to build.
|
||||||
|
pub struct WasmBuilderSelectProject {
|
||||||
|
/// This parameter just exists to make it impossible to construct
|
||||||
|
/// this type outside of this crate.
|
||||||
|
_ignore: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmBuilderSelectProject {
|
||||||
|
/// Use the current project as project for building the WASM binary.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the `CARGO_MANIFEST_DIR` variable is not set. This variable
|
||||||
|
/// is always set by `Cargo` in `build.rs` files.
|
||||||
|
pub fn with_current_project(self) -> WasmBuilderSelectSource {
|
||||||
|
WasmBuilderSelectSource(get_manifest_dir().join("Cargo.toml"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use the given `path` as project for building the WASM binary.
|
||||||
|
///
|
||||||
|
/// Returns an error if the given `path` does not points to a `Cargo.toml`.
|
||||||
|
pub fn with_project(
|
||||||
|
self,
|
||||||
|
path: impl Into<PathBuf>,
|
||||||
|
) -> Result<WasmBuilderSelectSource, &'static str> {
|
||||||
|
let path = path.into();
|
||||||
|
|
||||||
|
if path.ends_with("Cargo.toml") {
|
||||||
|
Ok(WasmBuilderSelectSource(path))
|
||||||
|
} else {
|
||||||
|
Err("Project path must point to the `Cargo.toml` of the project")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Second step of the [`WasmBuilder`] to set the source of the `wasm-builder`.
|
||||||
|
pub struct WasmBuilderSelectSource(PathBuf);
|
||||||
|
|
||||||
|
impl WasmBuilderSelectSource {
|
||||||
|
/// Use the given `path` as source for `wasm-builder`.
|
||||||
|
///
|
||||||
|
/// The `path` must be relative and point to the directory that contains the `Cargo.toml` for
|
||||||
|
/// `wasm-builder`.
|
||||||
|
pub fn with_wasm_builder_from_path(self, path: &'static str) -> WasmBuilder {
|
||||||
|
WasmBuilder {
|
||||||
|
source: WasmBuilderSource::Path(path),
|
||||||
|
rust_flags: Vec::new(),
|
||||||
|
file_name: None,
|
||||||
|
project_cargo_toml: self.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use the given `repo` and `rev` as source for `wasm-builder`.
|
||||||
|
pub fn with_wasm_builder_from_git(self, repo: &'static str, rev: &'static str) -> WasmBuilder {
|
||||||
|
WasmBuilder {
|
||||||
|
source: WasmBuilderSource::Git { repo, rev },
|
||||||
|
rust_flags: Vec::new(),
|
||||||
|
file_name: None,
|
||||||
|
project_cargo_toml: self.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use the given `version` to fetch `wasm-builder` source from crates.io.
|
||||||
|
pub fn with_wasm_builder_from_crates(self, version: &'static str) -> WasmBuilder {
|
||||||
|
WasmBuilder {
|
||||||
|
source: WasmBuilderSource::Crates(version),
|
||||||
|
rust_flags: Vec::new(),
|
||||||
|
file_name: None,
|
||||||
|
project_cargo_toml: self.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use the given `version` to fetch `wasm-builder` source from crates.io or use
|
||||||
|
/// the given `path` as source.
|
||||||
|
///
|
||||||
|
/// The `path` must be relative and point to the directory that contains the `Cargo.toml` for
|
||||||
|
/// `wasm-builder`.
|
||||||
|
pub fn with_wasm_builder_from_crates_or_path(
|
||||||
|
self,
|
||||||
|
version: &'static str,
|
||||||
|
path: &'static str,
|
||||||
|
) -> WasmBuilder {
|
||||||
|
WasmBuilder {
|
||||||
|
source: WasmBuilderSource::CratesOrPath { version, path },
|
||||||
|
rust_flags: Vec::new(),
|
||||||
|
file_name: None,
|
||||||
|
project_cargo_toml: self.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use the given `source` as source for `wasm-builder`.
|
||||||
|
pub fn with_wasm_builder_source(self, source: WasmBuilderSource) -> WasmBuilder {
|
||||||
|
WasmBuilder {
|
||||||
|
source,
|
||||||
|
rust_flags: Vec::new(),
|
||||||
|
file_name: None,
|
||||||
|
project_cargo_toml: self.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The builder for building a wasm binary.
|
||||||
|
///
|
||||||
|
/// The builder itself is seperated into multiple structs to make the setup type safe.
|
||||||
|
///
|
||||||
|
/// Building a wasm binary:
|
||||||
|
///
|
||||||
|
/// 1. Call [`WasmBuilder::new`] to create a new builder.
|
||||||
|
/// 2. Select the project to build using the methods of [`WasmBuilderSelectProject`].
|
||||||
|
/// 3. Select the source of the `wasm-builder` crate using the methods of
|
||||||
|
/// [`WasmBuilderSelectSource`].
|
||||||
|
/// 4. Set additional `RUST_FLAGS` or a different name for the file containing the WASM code
|
||||||
|
/// using methods of [`Self`].
|
||||||
|
/// 5. Build the WASM binary using [`Self::build`].
|
||||||
|
pub struct WasmBuilder {
|
||||||
|
/// Where should we pull the `wasm-builder` crate from.
|
||||||
|
source: WasmBuilderSource,
|
||||||
|
/// Flags that should be appended to `RUST_FLAGS` env variable.
|
||||||
|
rust_flags: Vec<String>,
|
||||||
|
/// The name of the file that is being generated in `OUT_DIR`.
|
||||||
|
///
|
||||||
|
/// Defaults to `wasm_binary.rs`.
|
||||||
|
file_name: Option<String>,
|
||||||
|
/// The path to the `Cargo.toml` of the project that should be build
|
||||||
|
/// for wasm.
|
||||||
|
project_cargo_toml: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmBuilder {
|
||||||
|
/// Create a new instance of the builder.
|
||||||
|
pub fn new() -> WasmBuilderSelectProject {
|
||||||
|
WasmBuilderSelectProject {
|
||||||
|
_ignore: (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable exporting `__heap_base` as global variable in the WASM binary.
|
||||||
|
///
|
||||||
|
/// This adds `-Clink-arg=--export=__heap_base` to `RUST_FLAGS`.
|
||||||
|
pub fn export_heap_base(mut self) -> Self {
|
||||||
|
self.rust_flags.push("-Clink-arg=--export=__heap_base".into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the name of the file that will be generated in `OUT_DIR`.
|
||||||
|
///
|
||||||
|
/// This file needs to be included to get access to the build WASM binary.
|
||||||
|
///
|
||||||
|
/// If this function is not called, `file_name` defaults to `wasm_binary.rs`
|
||||||
|
pub fn set_file_name(mut self, file_name: impl Into<String>) -> Self {
|
||||||
|
self.file_name = Some(file_name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Instruct the linker to import the memory into the WASM binary.
|
||||||
|
///
|
||||||
|
/// This adds `-C link-arg=--import-memory` to `RUST_FLAGS`.
|
||||||
|
pub fn import_memory(mut self) -> Self {
|
||||||
|
self.rust_flags.push("-C link-arg=--import-memory".into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append the given `flag` to `RUST_FLAGS`.
|
||||||
|
///
|
||||||
|
/// `flag` is appended as is, so it needs to be a valid flag.
|
||||||
|
pub fn append_to_rust_flags(mut self, flag: impl Into<String>) -> Self {
|
||||||
|
self.rust_flags.push(flag.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the WASM binary.
|
||||||
|
pub fn build(self) {
|
||||||
|
if check_skip_build() {
|
||||||
|
// If we skip the build, we still want to make sure to be called when an env variable
|
||||||
|
// changes
|
||||||
|
generate_rerun_if_changed_instructions();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is set by cargo!"));
|
||||||
|
let file_path = out_dir.join(self.file_name.unwrap_or_else(|| "wasm_binary.rs".into()));
|
||||||
|
|
||||||
|
// Hash the path to the project cargo toml.
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
self.project_cargo_toml.hash(&mut hasher);
|
||||||
|
|
||||||
|
let project_name = env::var("CARGO_PKG_NAME").expect("`CARGO_PKG_NAME` is set by cargo!");
|
||||||
|
// Make sure the `wasm-builder-runner` path is unique by concatenating the name of the
|
||||||
|
// project that is compiling the WASM binary with the hash of the path to the project that
|
||||||
|
// should be compiled as WASM binary.
|
||||||
|
let project_folder = get_workspace_root()
|
||||||
|
.join(format!("{}{}", project_name, hasher.finish()));
|
||||||
|
|
||||||
|
if check_provide_dummy_wasm_binary() {
|
||||||
|
provide_dummy_wasm_binary(&file_path);
|
||||||
|
} else {
|
||||||
|
create_project(
|
||||||
|
&project_folder,
|
||||||
|
&file_path,
|
||||||
|
self.source,
|
||||||
|
&self.project_cargo_toml,
|
||||||
|
&self.rust_flags.into_iter().map(|f| format!("{} ", f)).collect::<String>(),
|
||||||
|
);
|
||||||
|
run_project(&project_folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// As last step we need to generate our `rerun-if-changed` stuff. If a build fails, we don't
|
||||||
|
// want to spam the output!
|
||||||
|
generate_rerun_if_changed_instructions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The `wasm-builder` dependency source.
|
/// The `wasm-builder` dependency source.
|
||||||
pub enum WasmBuilderSource {
|
pub enum WasmBuilderSource {
|
||||||
/// The relative path to the source code from the current manifest dir.
|
/// The relative path to the source code from the current manifest dir.
|
||||||
@@ -96,46 +318,21 @@ impl WasmBuilderSource {
|
|||||||
/// Build the currently built project as WASM binary and extend `RUSTFLAGS` with the given rustflags.
|
/// Build the currently built project as WASM binary and extend `RUSTFLAGS` with the given rustflags.
|
||||||
///
|
///
|
||||||
/// For more information, see [`build_current_project`].
|
/// For more information, see [`build_current_project`].
|
||||||
|
#[deprecated(
|
||||||
|
since = "1.0.5",
|
||||||
|
note = "Please switch to [`WasmBuilder`]",
|
||||||
|
)]
|
||||||
pub fn build_current_project_with_rustflags(
|
pub fn build_current_project_with_rustflags(
|
||||||
file_name: &str,
|
file_name: &str,
|
||||||
wasm_builder_source: WasmBuilderSource,
|
wasm_builder_source: WasmBuilderSource,
|
||||||
default_rustflags: &str,
|
default_rust_flags: &str,
|
||||||
) {
|
) {
|
||||||
if check_skip_build() {
|
WasmBuilder::new()
|
||||||
// If we skip the build, we still want to make sure to be called when an env variable changes
|
.with_current_project()
|
||||||
generate_rerun_if_changed_instructions();
|
.with_wasm_builder_source(wasm_builder_source)
|
||||||
return;
|
.append_to_rust_flags(default_rust_flags)
|
||||||
}
|
.set_file_name(file_name)
|
||||||
|
.build()
|
||||||
let manifest_dir = PathBuf::from(
|
|
||||||
env::var("CARGO_MANIFEST_DIR").expect(
|
|
||||||
"`CARGO_MANIFEST_DIR` is always set for `build.rs` files; qed"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let cargo_toml_path = manifest_dir.join("Cargo.toml");
|
|
||||||
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is set by cargo!"));
|
|
||||||
let file_path = out_dir.join(file_name);
|
|
||||||
let project_name = env::var("CARGO_PKG_NAME").expect("`CARGO_PKG_NAME` is set by cargo!");
|
|
||||||
let project_folder = get_workspace_root().join(project_name);
|
|
||||||
|
|
||||||
if check_provide_dummy_wasm_binary() {
|
|
||||||
provide_dummy_wasm_binary(&file_path);
|
|
||||||
} else {
|
|
||||||
create_project(
|
|
||||||
&project_folder,
|
|
||||||
&file_path,
|
|
||||||
&manifest_dir,
|
|
||||||
wasm_builder_source,
|
|
||||||
&cargo_toml_path,
|
|
||||||
default_rustflags,
|
|
||||||
);
|
|
||||||
run_project(&project_folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
// As last step we need to generate our `rerun-if-changed` stuff. If a build fails, we don't
|
|
||||||
// want to spam the output!
|
|
||||||
generate_rerun_if_changed_instructions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the currently built project as WASM binary.
|
/// Build the currently built project as WASM binary.
|
||||||
@@ -145,7 +342,12 @@ pub fn build_current_project_with_rustflags(
|
|||||||
/// `file_name` - The name of the file being generated in the `OUT_DIR`. The file contains the
|
/// `file_name` - The name of the file being generated in the `OUT_DIR`. The file contains the
|
||||||
/// constant `WASM_BINARY` which contains the build wasm binary.
|
/// constant `WASM_BINARY` which contains the build wasm binary.
|
||||||
/// `wasm_builder_path` - Path to the wasm-builder project, relative to `CARGO_MANIFEST_DIR`.
|
/// `wasm_builder_path` - Path to the wasm-builder project, relative to `CARGO_MANIFEST_DIR`.
|
||||||
|
#[deprecated(
|
||||||
|
since = "1.0.5",
|
||||||
|
note = "Please switch to [`WasmBuilder`]",
|
||||||
|
)]
|
||||||
pub fn build_current_project(file_name: &str, wasm_builder_source: WasmBuilderSource) {
|
pub fn build_current_project(file_name: &str, wasm_builder_source: WasmBuilderSource) {
|
||||||
|
#[allow(deprecated)]
|
||||||
build_current_project_with_rustflags(file_name, wasm_builder_source, "");
|
build_current_project_with_rustflags(file_name, wasm_builder_source, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +373,6 @@ fn get_workspace_root() -> PathBuf {
|
|||||||
fn create_project(
|
fn create_project(
|
||||||
project_folder: &Path,
|
project_folder: &Path,
|
||||||
file_path: &Path,
|
file_path: &Path,
|
||||||
manifest_dir: &Path,
|
|
||||||
wasm_builder_source: WasmBuilderSource,
|
wasm_builder_source: WasmBuilderSource,
|
||||||
cargo_toml_path: &Path,
|
cargo_toml_path: &Path,
|
||||||
default_rustflags: &str,
|
default_rustflags: &str,
|
||||||
@@ -193,7 +394,7 @@ fn create_project(
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
"#,
|
"#,
|
||||||
wasm_builder_source = wasm_builder_source.to_cargo_source(manifest_dir),
|
wasm_builder_source = wasm_builder_source.to_cargo_source(&get_manifest_dir()),
|
||||||
)
|
)
|
||||||
).expect("WASM build runner `Cargo.toml` writing can not fail; qed");
|
).expect("WASM build runner `Cargo.toml` writing can not fail; qed");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user