-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Move the allocator inside of the runtime #11883
Comments
Another disadvantage of the current approach is that we can't effectively make any non-trivial behavior affecting changes to the current allocator. There are various improvements to the allocator's internals which would be nice to make, however due to backward-compatibility concerns that carries with it a lot of risk. Since the allocator is provided by the host any changes to its behavior will affect all of the historical blocks, and those blocks might depend on the allocator's exact behavior due to Hyrum's law. Even if we can guarantee that our chains won't be affected we can't do that for every other parachain and project building on Substrate. Of course, an alternative to this would be to just use our existing host function versioning machinery and provide a So I'm highly in favor of this. There might have been good reasons to keep the allocator on the host's side in the past; today I don't really see why we wouldn't want to move it into the runtime. |
How would the host allocate the return value of a host function? |
That is a good question. That is currently one of the biggest issues which we'd need to resolve, since currently some types marshaled across the FFI boundary are directly allocated by the client itself inside of WASM's linear memory. Some random ideas off the top of my head (not all are necessarily great ideas; just typing out whatever comes to my mind):
It should be doable. Of course we'd need to make sure that this doesn't regress the performance and that whatever we'll pick will be maintainable into the future. |
Since this is not new, back in the day, I thought we would go with something like @koute described in the 2nd point. Specifically, I imagined that the host API would gain a function that would initialize the allocator. That function accepted parameters for function pointers to However, if possible, I would like to avoid reentrance into the same wasm instance 1. One annoying thing is the stack, especially in the current implementation of wasmtime. That's because the stack limit is shared between nested calls and the host functions. Maybe it's fine for Substrate, but PVF requires it to be robust against malicious code. We now live in 2022 and can think about enabling new features.
So yeah, it's doable. There are plenty of options. The option we pick should be robust to malicious PVFs. Footnotes
|
No, nothing of this was designed so far. |
TBH, I have designed this part before, I could just port it into substrate. Of course, it is only for substrate, and the polkadot part may be more complicated. After all, there is a verification logic for running parachains, but the thinking should be the same. Need a draft PR to show the idea? |
Can you give some brief overview of your design? |
The overviewFirstly we should disable host allocator and export runtime allocator. So define an new allocator crate [dependencies]
lol_alloc = { version = "0.3", optional = true }
# not used just enable feature
sp-io = { version = "7.0.0", path = "../io", default-features = false }
[features]
default = ["std"]
std = [
"sp-io/std",
]
disable_oom = [
"sp-io/disable_oom",
]
allocator-v1 = [
"lol_alloc",
"sp-io/disable_allocator",
# TODO: This part need to be redesigned
"sp-io/disable_panic_handler",
] The crate maybe have the following code: use core::alloc::GlobalAlloc;
use lol_alloc::{AssumeSingleThreaded, FreeListAllocator};
#[global_allocator]
pub static ALLOCATOR: AssumeSingleThreaded<FreeListAllocator> =
unsafe { AssumeSingleThreaded::new(FreeListAllocator::new()) };
#[no_mangle]
unsafe fn alloc(size: usize) -> *mut u8 {
ALLOCATOR.alloc(core::alloc::Layout::array::<u8>(size).unwrap())
}
#[no_mangle]
unsafe fn dealloc(ptr: *mut u8, size: usize) {
ALLOCATOR.dealloc(ptr, core::alloc::Layout::array::<u8>(size).unwrap())
}
#[no_mangle]
unsafe fn realloc(ptr: *mut u8, size: usize, new_size: usize) -> *mut u8 {
ALLOCATOR.realloc(ptr, core::alloc::Layout::array::<u8>(size).unwrap(), new_size)
}
// TODO: maybe it's better to rename this crate to `sp-runtime-abi`.
/// The dummy function represents the version of runtime ABI.
///
/// This name should be checked by host.
#[no_mangle]
fn v1() {
// nop
}
/// A default panic handler for WASM environment.
#[panic_handler]
#[no_mangle]
pub fn panic(_info: &core::panic::PanicInfo) -> ! {
core::arch::wasm32::unreachable()
}
/// A default OOM handler for WASM environment.
#[alloc_error_handler]
pub fn oom(_layout: core::alloc::Layout) -> ! {
core::arch::wasm32::unreachable()
} The versioning here only manages the specification of exported primitive functions, of course, I think it can also cover more specifications. And then we could import this crate for sp-allocator-v1 = { version = "7.0", default-features = false, path = "../../../primitives/allocator-v1" }
# ...
[features]
default = ["std"]
allocator-v1 = [
"sp-allocator-v1/allocator-v1",
]
# ... And enable it Now we could build a substrate wasm runtime without host allocator. And since we mark the version info in exported function name(Or mark it in custom section, but I think the former design is better), we could refactor Maybe define a /// The Abi version for runtime.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum AbiVersion {
/// Legacy allocator defined by host.
#[default]
Legacy,
/// V1 see sp-allocator-v1.
V1,
} |
Try to build node-template-runtime here: https://github.com/yjhmelody/substrate/tree/executor-inside-allocator
Now the wasm executor not work anymore, since
|
IMO it's not necessary to add yet another version number to this. The client should just check whether the runtime exports a function named However, in my opinion it wouldn't be a bad idea to also transition host functions to not perform allocations at all. This is very straight forward for the The only not-so-simple ones could be the |
The current implementation obviously needs
For this part, I think it could not prevent the idea about exporting |
What I mean is that the client never frees the memory it allocates, it's always the runtime that does that, so there's no need for the runtime to export this function. |
Oh, yeah, You are right. |
Whether it is hard or not is completely irrelevant. There exists a certain interface between the client and the runtime, and exporting |
It is actually not a new idea and was considered very early. We ended up having the allocator provided by the host.
Lately, there were several discussions about moving the allocator back into the runtime.
The text was updated successfully, but these errors were encountered: