Skip to content

Commit 346afb1

Browse files
committed
jit: Enable JIT without "std" feature
Add a new "alloc" feature that indicates that the crate importing rbpf has an implementation of the GlobalAlloc trait. This feature is mutually exclusive with "std". Memory protection is ignored in the "no_std" environment and the helper functions are compiled out. Tests have been updated to account for the mutual exclusivity of the "alloc" and "std" features. Signed-off-by: Nate Sweet <nathanjsweet@pm.me>
1 parent f31e471 commit 346afb1

File tree

6 files changed

+99
-19
lines changed

6 files changed

+99
-19
lines changed

.github/workflows/test.yaml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
fail-fast: false
1919
matrix:
2020
toolchain: [stable, beta, nightly]
21-
features: [--all-features]
21+
features: [--features "std cranelift"]
2222
include:
2323
- toolchain: nightly
2424
features: --no-default-features
@@ -75,7 +75,8 @@ jobs:
7575

7676
- name: Build plug-in
7777
run: |
78-
cargo build --all-features --release --example rbpf_plugin
78+
cargo build --no-default-features --features "std cranelift" --release --example rbpf_plugin
79+
cargo build --no-default-features --features "alloc" --release --example rbpf_plugin_alloc
7980
8081
- name: Run BPF conformance tests - Interpreter
8182
run: |
@@ -94,6 +95,15 @@ jobs:
9495
--plugin_path /rbpf/target/release/examples/rbpf_plugin \
9596
--exclude_regex "${{ env.KNOWN_FAILURES }}" \
9697
--plugin_options "--jit" || true
98+
99+
- name: Run BPF conformance tests Alloc feature - JIT
100+
run: |
101+
docker run -v ${{github.workspace}}:/rbpf --rm \
102+
"${{ env.CONFORMANCE_IMAGE }}" \
103+
bin/bpf_conformance_runner --test_file_directory tests \
104+
--plugin_path /rbpf/target/release/examples/rbpf_plugin_alloc \
105+
--exclude_regex "${{ env.KNOWN_FAILURES }}" \
106+
--plugin_options "--jit" || true
97107
98108
- name: Run BPF conformance tests - Cranelift
99109
run: |
@@ -111,7 +121,8 @@ jobs:
111121

112122
- name: Generate coverage report
113123
run: |
114-
cargo llvm-cov --lcov --all --all-features --all-targets > lcov.info
124+
cargo build --no-default-features --features "std cranelift" --release --example rbpf_plugin
125+
cargo llvm-cov --no-default-features --features "alloc" --lcov --all --all-targets > lcov_alloc.info
115126
116127
- name: Upload coverage to coveralls
117128
uses: coverallsapp/github-action@master

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ combine = { version = "4.6", default-features = false }
3535
libc = { version = "0.2", optional = true }
3636
time = { version = "0.2", optional = true }
3737

38+
# Optional Dependencies for using alloc
39+
no-std-compat = {version = "0.4.1", optional = true }
40+
3841
# Optional Dependencies for the CraneLift JIT
3942
cranelift-codegen = { version = "0.99", optional = true }
4043
cranelift-frontend = { version = "0.99", optional = true }
@@ -52,6 +55,7 @@ hex = "0.4.3"
5255
[features]
5356
default = ["std"]
5457
std = ["dep:time", "dep:libc", "combine/std"]
58+
alloc = ["dep:no-std-compat"]
5559
cranelift = [
5660
"dep:cranelift-codegen",
5761
"dep:cranelift-frontend",

README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,10 @@ get more information and examples on how to use it.
568568

569569
## Build features
570570

571-
### `no_std`
571+
> [!WARNING]
572+
> To rbpf developers: The `std` and `alloc` features are mutually exclusive. If you want to add [a feature](https://doc.rust-lang.org/cargo/reference/features.html) to this codebase you need to make sure to update the feature matrix in [the CI](https://github.com/qmonnet/rbpf/blob/main/.github/workflows/test.yaml#L21).
573+
574+
### `std`
572575

573576
The `rbpf` crate has a Cargo feature named "std" that is enabled by default. To
574577
use `rbpf` in `no_std` environments this feature needs to be disabled. To do
@@ -581,15 +584,25 @@ rbpf = { version = "0.3.0", default-features = false }
581584
```
582585

583586
Note that when using this crate in `no_std` environments, the `jit` module
584-
isn't available. This is because it depends on functions provided by `libc`
585-
(`libc::posix_memalign()`, `libc::mprotect()`) which aren't available on
586-
`no_std`.
587+
isn't available, unless the `alloc` feature is turned on. This is because
588+
it depends on allocation being memory aligned and a memory protection function
589+
provided by `libc` (`libc::mprotect()`) which isn't available on `no_std`.
587590

588591
The `assembler` module is available, albeit with reduced debugging features. It
589592
depends on the `combine` crate providing parser combinators. Under `no_std`
590593
this crate only provides simple parsers which generate less descriptive error
591594
messages.
592595

596+
### `alloc`
597+
598+
The `rbpf` crate has a Cargo feature named "alloc" that is not enabled by
599+
default. `std` must be turned off and is mutually exclusive with alloc.
600+
When `alloc` is turned on the code importing rbpf must provide an
601+
implementation of the [`GlobalAlloc` trait](https://doc.rust-lang.org/beta/std/alloc/trait.GlobalAlloc.html).
602+
All JIT examples will work that do not require the "std" feature.
603+
The `time` and `rand` functions will not be available, for example.
604+
605+
593606
## Feedback welcome!
594607

595608
This is the author's first try at writing Rust code. He learned a lot in the

examples/rbpf_plugin.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
// Path: examples/rbpf_plugin.rs
55
use std::io::Read;
66

7+
#[cfg(all(not(windows), not(feature = "std"), feature = "alloc"))]
8+
use std::alloc::System;
9+
#[cfg(all(not(windows), not(feature = "std"), feature = "alloc"))]
10+
#[global_allocator]
11+
static GLOBAL: System = System;
12+
713
// Helper function used by https://github.com/Alan-Jowett/bpf_conformance/blob/main/tests/call_unwind_fail.data
814
fn _unwind(a: u64, _b: u64, _c: u64, _d: u64, _e: u64) -> u64 {
915
a
@@ -38,12 +44,12 @@ fn main() {
3844
return;
3945
}
4046
"--jit" => {
41-
#[cfg(any(windows, not(feature = "std")))]
47+
#[cfg(not(any(feature = "std", feature = "alloc")))]
4248
{
4349
println!("JIT not supported");
4450
return;
4551
}
46-
#[cfg(all(not(windows), feature = "std"))]
52+
#[cfg(all(not(windows), any(feature = "std", feature = "alloc")))]
4753
{
4854
jit = true;
4955
}
@@ -96,12 +102,12 @@ fn main() {
96102

97103
let result: u64;
98104
if jit {
99-
#[cfg(any(windows, not(feature = "std")))]
105+
#[cfg(not(any(feature = "std", feature = "alloc")))]
100106
{
101107
println!("JIT not supported");
102108
return;
103109
}
104-
#[cfg(all(not(windows), feature = "std"))]
110+
#[cfg(all(not(windows), any(feature = "std", feature = "alloc")))]
105111
{
106112
unsafe {
107113
vm.jit_compile().unwrap();

src/jit.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,8 @@ pub struct JitMemory<'a> {
10101010
contents: &'a mut [u8],
10111011
#[cfg(feature = "std")]
10121012
layout: std::alloc::Layout,
1013+
#[cfg(feature = "alloc")]
1014+
layout: core::alloc::Layout,
10131015
offset: usize,
10141016
}
10151017

@@ -1055,7 +1057,45 @@ impl<'a> JitMemory<'a> {
10551057
Ok(mem)
10561058
}
10571059

1058-
#[cfg(not(feature = "std"))]
1060+
#[cfg(feature = "alloc")]
1061+
pub fn new(
1062+
prog: &[u8],
1063+
helpers: &HashMap<u32, ebpf::Helper>,
1064+
use_mbuff: bool,
1065+
update_data_ptr: bool,
1066+
) -> Result<JitMemory<'a>, Error> {
1067+
let layout;
1068+
1069+
// Allocate the appropriately sized memory.
1070+
let contents = unsafe {
1071+
// Create a layout with the proper size and alignment.
1072+
let size = NUM_PAGES * PAGE_SIZE;
1073+
layout = core::alloc::Layout::from_size_align_unchecked(size, PAGE_SIZE);
1074+
1075+
// Allocate the region of memory.
1076+
let ptr = alloc::alloc::alloc(layout);
1077+
if ptr.is_null() {
1078+
return Err(Error::new(ErrorKind::Other, "Out of memory"));
1079+
}
1080+
1081+
// Convert to a slice.
1082+
no_std_compat::slice::from_raw_parts_mut(ptr, size)
1083+
};
1084+
1085+
let mut mem = JitMemory {
1086+
contents,
1087+
layout,
1088+
offset: 0,
1089+
};
1090+
1091+
let mut jit = JitCompiler::new();
1092+
jit.jit_compile(&mut mem, prog, use_mbuff, update_data_ptr, helpers)?;
1093+
jit.resolve_jumps(&mut mem)?;
1094+
1095+
Ok(mem)
1096+
}
1097+
1098+
#[cfg(not(any(feature = "std", feature = "alloc")))]
10591099
pub fn new(
10601100
prog: &[u8],
10611101
executable_memory: &'a mut [u8],
@@ -1108,11 +1148,14 @@ impl IndexMut<usize> for JitMemory<'_> {
11081148
}
11091149
}
11101150

1111-
#[cfg(feature = "std")]
1151+
#[cfg(any(feature = "std", feature = "alloc"))]
11121152
impl Drop for JitMemory<'_> {
11131153
fn drop(&mut self) {
11141154
unsafe {
1155+
#[cfg(feature = "std")]
11151156
std::alloc::dealloc(self.contents.as_mut_ptr(), self.layout);
1157+
#[cfg(feature = "alloc")]
1158+
alloc::alloc::dealloc(self.contents.as_mut_ptr(), self.layout);
11161159
}
11171160
}
11181161
}

src/lib.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
// Configures the crate to be `no_std` when `std` feature is disabled.
1414
#![cfg_attr(not(feature = "std"), no_std)]
1515

16+
#[cfg(all(feature = "std", feature = "alloc"))]
17+
compile_error!("feature \"std\" and feature \"alloc\" cannot be enabled at the same time");
18+
1619
extern crate byteorder;
1720
extern crate combine;
1821
extern crate log;
@@ -510,11 +513,11 @@ impl<'a> EbpfVmMbuff<'a> {
510513
"Error: No program set, call prog_set() to load one",
511514
))?,
512515
};
513-
#[cfg(feature = "std")]
516+
#[cfg(any(feature = "std", feature = "alloc"))]
514517
{
515518
self.jit = Some(jit::JitMemory::new(prog, &self.helpers, true, false)?);
516519
}
517-
#[cfg(not(feature = "std"))]
520+
#[cfg(not(any(feature = "std", feature = "alloc")))]
518521
{
519522
let exec_memory = match self.custom_exec_memory.take() {
520523
Some(memory) => memory,
@@ -1141,11 +1144,11 @@ impl<'a> EbpfVmFixedMbuff<'a> {
11411144
"Error: No program set, call prog_set() to load one",
11421145
))?,
11431146
};
1144-
#[cfg(feature = "std")]
1147+
#[cfg(any(feature = "std", feature = "alloc"))]
11451148
{
11461149
self.parent.jit = Some(jit::JitMemory::new(prog, &self.parent.helpers, true, true)?);
11471150
}
1148-
#[cfg(not(feature = "std"))]
1151+
#[cfg(not(any(feature = "std", feature = "alloc")))]
11491152
{
11501153
let exec_memory = match self.parent.custom_exec_memory.take() {
11511154
Some(memory) => memory,
@@ -1661,7 +1664,7 @@ impl<'a> EbpfVmRaw<'a> {
16611664
"Error: No program set, call prog_set() to load one",
16621665
))?,
16631666
};
1664-
#[cfg(feature = "std")]
1667+
#[cfg(any(feature = "std", feature = "alloc"))]
16651668
{
16661669
self.parent.jit = Some(jit::JitMemory::new(
16671670
prog,
@@ -1670,7 +1673,7 @@ impl<'a> EbpfVmRaw<'a> {
16701673
false,
16711674
)?);
16721675
}
1673-
#[cfg(not(feature = "std"))]
1676+
#[cfg(not(any(feature = "std", feature = "alloc")))]
16741677
{
16751678
let exec_memory = match self.parent.custom_exec_memory.take() {
16761679
Some(memory) => memory,

0 commit comments

Comments
 (0)