BPF, SBF, AVM, WASM

For starters BPF is typically very fast due to its direct interaction with the kernel and optimized execution environment, on the other hand WASM can be fast, but its performance depends on the specific Wasm runtime and the complexity of the code. Additionally, sandboxing might add some overhead.

  • eBPF, BPF: Runs inside the Linux kernel, allowing direct access to system resources and fine-grained control over kernel operations. This makes it powerful for tasks like network filtering, security monitoring, and performance optimization. Offers deep access to kernel resources and functionalities, including networking, security, tracing, and file systems. This enables powerful customization and optimization of the kernel.

  • Wasm: Runs in user space, sandboxed from the operating system and other applications. This provides security and stability, making it suitable for running untrusted code within web browsers or cloud environments. Offers limited access to system resources, focusing on running sandboxed code for web applications, serverless functions, and portable runtime environments.

  • AVM is our custom binary format inspired by BPF and have better trade off then both WASM and BPF in our docs we use AVM and BPF VM interchangeability.

Vanilla eBPF is not a really welcoming environment to write contracts. Since this tech originated from the kernel, it limited the space of expressable control flow. Specifically, it limited the usage of loops. People would use C with special macros to specify bounds for each loop. That immediately constraints the usefulness of the whole thing.

To avoid this, we decided to use the tech from Solana. Namely, they forked rust/llvm and rbpf 10.

It does not end here. Even though the hash values can be computed upfront, Solana’s runtime depended on dynamic relocations. That is, all syscalls would be emitted as

LABEL_001: call -1 # my_syscall

paired with a relocation entry that specifies that the particular my_syscall is called at LABEL_001. When the ELF is loaded prior to the execution, the loader would go over all relocations, compute hash, in this instance of my_syscall, then write fixup all values from -1 to the actual hash value.

To be fair, Solana folks do realize the problem since there seem to be some feature flags, that compute those hashes offline. But for now, it appears to be off by default.

Wasm typically catches out-of-bound memory accesses after the fact, being almost zero-cost. My biggest incentivation for eBPF over Wasm is the ability to write efficient and simple single pass compilers. Interpretation is the main contributor by a large margin why contracts are much much slower than the runtime itself. eBPF is easier to compile because it is a register machine just like any target machine we ever intent su support (x86 + ARM at the moment). It is a RISC ISA with few registers so that it can be 1to1 mapped to x86-64 and ARM without any register allocation overhead.

The other big incentive is simplicity: Wasm has a lot of high level constructs. Those are nice for analyzing the code and staying portable. However, it makes compilers and interpeters complicated. Single pass compilers outright slow. Also, validation of Wasm code is complicated and a huge attack surfaces. Case in point: A call instruction in Wasm is not constant time on almost all execution engines (because of the high level concept of local variables).

Solana went ahead and forked the eBPF backend of LLVM. We probably want to go the same route. Theoretically, this does not seem too hard as it is mainly removing some checks. But we don’t know which other changes they did as there is no documentation. The obvious downside of this is that they have to maintain a custom Rust toolchain and deliver it to their users.

There is the temptation to make further modifications when forking. We need to make sure that it is worth the hassle, though. It might be even conceivable to modify the upstream backend to add some options that allow us to bend BPF to our needs:

  1. Disable static termination checks

  2. Change memory mapping (might already be possible with bpf-linker) to linearly map everything to low memory

  3. Disable relocations (we probably can just dump the text section and establish some convention for a magic arg to call that is the syscall)

Solana is currently moving towards new system called SBF (Solana Binary Format) and will be used instead of BPF

Last updated