Skip to main content

soil_client/executor/common/runtime_blob/
runtime_blob.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7use crate::executor::common::{error::WasmError, wasm_runtime::HeapAllocStrategy};
8use polkavm::ArcBytes;
9use wasm_instrument::parity_wasm::elements::{
10	deserialize_buffer, serialize, ExportEntry, External, Internal, MemorySection, MemoryType,
11	Module, Section,
12};
13
14/// A program blob containing a Substrate runtime.
15#[derive(Clone)]
16pub struct RuntimeBlob(BlobKind);
17
18#[derive(Clone)]
19enum BlobKind {
20	WebAssembly(Module),
21	PolkaVM((polkavm::ProgramBlob, ArcBytes)),
22}
23
24impl RuntimeBlob {
25	/// Create `RuntimeBlob` from the given WASM or PolkaVM compressed program blob.
26	///
27	/// See [`crate::maybe_compressed_blob`] for details about decompression.
28	pub fn uncompress_if_needed(wasm_code: &[u8]) -> Result<Self, WasmError> {
29		use crate::maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT;
30		let wasm_code = crate::maybe_compressed_blob::decompress(wasm_code, CODE_BLOB_BOMB_LIMIT)
31			.map_err(|e| WasmError::Other(format!("Decompression error: {:?}", e)))?;
32		Self::new(&wasm_code)
33	}
34
35	/// Create `RuntimeBlob` from the given WASM or PolkaVM program blob.
36	///
37	/// Returns `Err` if the blob cannot be deserialized.
38	///
39	/// Will only accept a PolkaVM program if the `SUBSTRATE_ENABLE_POLKAVM` environment
40	/// variable is set to `1`.
41	pub fn new(raw_blob: &[u8]) -> Result<Self, WasmError> {
42		if raw_blob.starts_with(b"PVM\0") {
43			if crate::executor::common::is_polkavm_enabled() {
44				let raw = ArcBytes::from(raw_blob);
45				let blob = polkavm::ProgramBlob::parse(raw.clone())?;
46				return Ok(Self(BlobKind::PolkaVM((blob, raw))));
47			} else {
48				return Err(WasmError::Other("expected a WASM runtime blob, found a PolkaVM runtime blob; set the 'SUBSTRATE_ENABLE_POLKAVM' environment variable to enable the experimental PolkaVM-based executor".to_string()));
49			}
50		}
51
52		let raw_module: Module = deserialize_buffer(raw_blob)
53			.map_err(|e| WasmError::Other(format!("cannot deserialize module: {:?}", e)))?;
54		Ok(Self(BlobKind::WebAssembly(raw_module)))
55	}
56
57	/// Run a pass that instrument this module so as to introduce a deterministic stack height
58	/// limit.
59	///
60	/// It will introduce a global mutable counter. The instrumentation will increase the counter
61	/// according to the "cost" of the callee. If the cost exceeds the `stack_depth_limit` constant,
62	/// the instrumentation will trap. The counter will be decreased as soon as the the callee
63	/// returns.
64	///
65	/// The stack cost of a function is computed based on how much locals there are and the maximum
66	/// depth of the wasm operand stack.
67	///
68	/// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
69	pub fn inject_stack_depth_metering(self, stack_depth_limit: u32) -> Result<Self, WasmError> {
70		let injected_module =
71			wasm_instrument::inject_stack_limiter(self.into_webassembly_blob()?, stack_depth_limit)
72				.map_err(|e| {
73					WasmError::Other(format!("cannot inject the stack limiter: {:?}", e))
74				})?;
75
76		Ok(Self(BlobKind::WebAssembly(injected_module)))
77	}
78
79	/// Converts a WASM memory import into a memory section and exports it.
80	///
81	/// Does nothing if there's no memory import.
82	///
83	/// May return an error in case the WASM module is invalid.
84	///
85	/// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
86	pub fn convert_memory_import_into_export(&mut self) -> Result<(), WasmError> {
87		let raw_module = self.as_webassembly_blob_mut()?;
88		let import_section = match raw_module.import_section_mut() {
89			Some(import_section) => import_section,
90			None => return Ok(()),
91		};
92
93		let import_entries = import_section.entries_mut();
94		for index in 0..import_entries.len() {
95			let entry = &import_entries[index];
96			let memory_ty = match entry.external() {
97				External::Memory(memory_ty) => *memory_ty,
98				_ => continue,
99			};
100
101			let memory_name = entry.field().to_owned();
102			import_entries.remove(index);
103
104			raw_module
105				.insert_section(Section::Memory(MemorySection::with_entries(vec![memory_ty])))
106				.map_err(|error| {
107					WasmError::Other(format!(
108					"can't convert a memory import into an export: failed to insert a new memory section: {}",
109					error
110				))
111				})?;
112
113			if raw_module.export_section_mut().is_none() {
114				// A module without an export section is somewhat unrealistic, but let's do this
115				// just in case to cover all of our bases.
116				raw_module
117					.insert_section(Section::Export(Default::default()))
118					.expect("an export section can be always inserted if it doesn't exist; qed");
119			}
120			raw_module
121				.export_section_mut()
122				.expect("export section already existed or we just added it above, so it always exists; qed")
123				.entries_mut()
124				.push(ExportEntry::new(memory_name, Internal::Memory(0)));
125
126			break;
127		}
128
129		Ok(())
130	}
131
132	/// Modifies the blob's memory section according to the given `heap_alloc_strategy`.
133	///
134	/// Will return an error in case there is no memory section present,
135	/// or if the memory section is empty.
136	///
137	/// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
138	pub fn setup_memory_according_to_heap_alloc_strategy(
139		&mut self,
140		heap_alloc_strategy: HeapAllocStrategy,
141	) -> Result<(), WasmError> {
142		let raw_module = self.as_webassembly_blob_mut()?;
143		let memory_section = raw_module
144			.memory_section_mut()
145			.ok_or_else(|| WasmError::Other("no memory section found".into()))?;
146
147		if memory_section.entries().is_empty() {
148			return Err(WasmError::Other("memory section is empty".into()));
149		}
150		for memory_ty in memory_section.entries_mut() {
151			let initial = memory_ty.limits().initial();
152			let (min, max) = match heap_alloc_strategy {
153				HeapAllocStrategy::Dynamic { maximum_pages } => {
154					// Ensure `initial <= maximum_pages`
155					(maximum_pages.map(|m| m.min(initial)).unwrap_or(initial), maximum_pages)
156				},
157				HeapAllocStrategy::Static { extra_pages } => {
158					let pages = initial.saturating_add(extra_pages);
159					(pages, Some(pages))
160				},
161			};
162			*memory_ty = MemoryType::new(min, max);
163		}
164		Ok(())
165	}
166
167	/// Scans the wasm blob for the first section with the name that matches the given. Returns the
168	/// contents of the custom section if found or `None` otherwise.
169	///
170	/// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
171	pub fn custom_section_contents(&self, section_name: &str) -> Option<&[u8]> {
172		self.as_webassembly_blob()
173			.ok()?
174			.custom_sections()
175			.find(|cs| cs.name() == section_name)
176			.map(|cs| cs.payload())
177	}
178
179	/// Consumes this runtime blob and serializes it.
180	pub fn serialize(self) -> Vec<u8> {
181		match self.0 {
182			BlobKind::WebAssembly(raw_module) => {
183				serialize(raw_module).expect("serializing into a vec should succeed; qed")
184			},
185			BlobKind::PolkaVM(ref blob) => blob.1.to_vec(),
186		}
187	}
188
189	fn as_webassembly_blob(&self) -> Result<&Module, WasmError> {
190		match self.0 {
191			BlobKind::WebAssembly(ref raw_module) => Ok(raw_module),
192			BlobKind::PolkaVM(..) => Err(WasmError::Other(
193				"expected a WebAssembly program; found a PolkaVM program blob".into(),
194			)),
195		}
196	}
197
198	fn as_webassembly_blob_mut(&mut self) -> Result<&mut Module, WasmError> {
199		match self.0 {
200			BlobKind::WebAssembly(ref mut raw_module) => Ok(raw_module),
201			BlobKind::PolkaVM(..) => Err(WasmError::Other(
202				"expected a WebAssembly program; found a PolkaVM program blob".into(),
203			)),
204		}
205	}
206
207	fn into_webassembly_blob(self) -> Result<Module, WasmError> {
208		match self.0 {
209			BlobKind::WebAssembly(raw_module) => Ok(raw_module),
210			BlobKind::PolkaVM(..) => Err(WasmError::Other(
211				"expected a WebAssembly program; found a PolkaVM program blob".into(),
212			)),
213		}
214	}
215
216	/// Gets a reference to the inner PolkaVM program blob, if this is a PolkaVM program.
217	pub fn as_polkavm_blob(&self) -> Option<&polkavm::ProgramBlob> {
218		match self.0 {
219			BlobKind::WebAssembly(..) => None,
220			BlobKind::PolkaVM((ref blob, _)) => Some(blob),
221		}
222	}
223}