Skip to content
This repository was archived by the owner on Mar 24, 2022. It is now read-only.

Commit 14d1c14

Browse files
authored
Merge pull request #77 from fastly/acf/lucet-wasi-memory-cli
Add CLI configuration for memory limits and improve performance for large initial heaps
2 parents a5f1583 + b563370 commit 14d1c14

File tree

17 files changed

+196
-61
lines changed

17 files changed

+196
-61
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lucet-runtime/lucet-runtime-internals/src/alloc/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,11 @@ impl Limits {
388388
"address space size must be a multiple of host page size",
389389
));
390390
}
391+
if self.heap_memory_size > self.heap_address_space_size {
392+
return Err(Error::InvalidArgument(
393+
"address space size must be at least as large as memory size",
394+
));
395+
}
391396
if self.stack_size % host_page_size() != 0 {
392397
return Err(Error::InvalidArgument(
393398
"stack size must be a multiple of host page size",

lucet-runtime/lucet-runtime-internals/src/alloc/tests.rs

+13
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,19 @@ macro_rules! alloc_tests {
256256
assert!(res.is_err(), "new_instance fails");
257257
}
258258

259+
/// This test shows that we reject limits with a larger memory size than address space size
260+
#[test]
261+
fn reject_undersized_address_space() {
262+
const LIMITS: Limits = Limits {
263+
heap_memory_size: LIMITS_HEAP_ADDRSPACE_SIZE + 4096,
264+
heap_address_space_size: LIMITS_HEAP_ADDRSPACE_SIZE,
265+
stack_size: LIMITS_STACK_SIZE,
266+
globals_size: LIMITS_GLOBALS_SIZE,
267+
};
268+
let res = TestRegion::create(10, &LIMITS);
269+
assert!(res.is_err(), "region creation fails");
270+
}
271+
259272
const SMALL_GUARD_HEAP: HeapSpec = HeapSpec {
260273
reserved_size: SPEC_HEAP_RESERVED_SIZE,
261274
guard_size: SPEC_HEAP_GUARD_SIZE - 1,

lucet-runtime/lucet-runtime-internals/src/module.rs

+7
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ pub trait ModuleInternal: Send + Sync {
164164
bail_limits_exceeded!("heap spec initial size: {:?}", heap);
165165
}
166166

167+
if heap.initial_size > heap.reserved_size {
168+
return Err(lucet_incorrect_module!(
169+
"initial heap size greater than reserved size: {:?}",
170+
heap
171+
));
172+
}
173+
167174
if self.globals().len() * std::mem::size_of::<u64>() > limits.globals_size {
168175
bail_limits_exceeded!("globals exceed limits");
169176
}

lucet-runtime/lucet-runtime-internals/src/region/mmap.rs

+23-26
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ impl RegionInternal for MmapRegion {
3939
module.validate_runtime_spec(limits)?;
4040

4141
for (ptr, len) in [
42-
// make the heap read/writable and record its initial size
43-
(slot.heap, module.heap_spec().initial_size as usize),
4442
// make the stack read/writable
4543
(slot.stack, limits.stack_size),
4644
// make the globals read/writable
@@ -54,6 +52,8 @@ impl RegionInternal for MmapRegion {
5452
unsafe { mprotect(*ptr, *len, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE)? };
5553
}
5654

55+
// note: the initial heap will be made read/writable when `new_instance_handle` calls `reset`
56+
5757
let inst_ptr = slot.start as *mut Instance;
5858

5959
// upgrade the slot's weak region pointer so the region can't get dropped while the instance
@@ -66,7 +66,7 @@ impl RegionInternal for MmapRegion {
6666
.expect("backing region of slot (`self`) exists");
6767

6868
let alloc = Alloc {
69-
heap_accessible_size: module.heap_spec().initial_size as usize,
69+
heap_accessible_size: 0, // the `reset` call in `new_instance_handle` will set this
7070
heap_inaccessible_size: slot.limits.heap_address_space_size,
7171
slot: Some(slot),
7272
region,
@@ -119,29 +119,32 @@ impl RegionInternal for MmapRegion {
119119
}
120120

121121
fn reset_heap(&self, alloc: &mut Alloc, module: &dyn Module) -> Result<(), Error> {
122+
let heap = alloc.slot().heap;
123+
124+
if alloc.heap_accessible_size > 0 {
125+
// zero the whole heap, if any of it is currently accessible
126+
let heap_size = alloc.slot().limits.heap_address_space_size;
127+
128+
unsafe {
129+
mprotect(heap, heap_size, ProtFlags::PROT_NONE)?;
130+
madvise(heap, heap_size, MmapAdvise::MADV_DONTNEED)?;
131+
}
132+
}
133+
122134
let initial_size = module.heap_spec().initial_size as usize;
123135

124-
// reset the heap to the initial size
136+
// reset the heap to the initial size, and mprotect those pages appropriately
125137
if alloc.heap_accessible_size != initial_size {
138+
unsafe {
139+
mprotect(
140+
heap,
141+
initial_size,
142+
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
143+
)?
144+
};
126145
alloc.heap_accessible_size = initial_size;
127146
alloc.heap_inaccessible_size =
128147
alloc.slot().limits.heap_address_space_size - initial_size;
129-
130-
// turn off any extra pages
131-
let acc_heap_end =
132-
(alloc.slot().heap as usize + alloc.heap_accessible_size) as *mut c_void;
133-
unsafe {
134-
mprotect(
135-
acc_heap_end,
136-
alloc.heap_inaccessible_size,
137-
ProtFlags::PROT_NONE,
138-
)?;
139-
madvise(
140-
acc_heap_end,
141-
alloc.heap_inaccessible_size,
142-
MmapAdvise::MADV_DONTNEED,
143-
)?;
144-
}
145148
}
146149

147150
// Initialize the heap using the module sparse page data. There cannot be more pages in the
@@ -171,12 +174,6 @@ impl RegionInternal for MmapRegion {
171174
if let Some(contents) = module.get_sparse_page_data(page_num) {
172175
// otherwise copy in the page data
173176
heap[page_base..page_base + host_page_size()].copy_from_slice(contents);
174-
} else {
175-
// zero this page
176-
// TODO would using a memset primitive be better here?
177-
for b in heap[page_base..page_base + host_page_size()].iter_mut() {
178-
*b = 0x00;
179-
}
180177
}
181178
}
182179

lucet-wasi/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ license = "Apache-2.0 WITH LLVM-exception"
99
cast = "0.2"
1010
clap = "2.23"
1111
failure = "0.1"
12+
human-size = "0.4"
1213
libc = "0.2"
1314
lucet-runtime = { path = "../lucet-runtime" }
1415
lucet-runtime-internals = { path = "../lucet-runtime/lucet-runtime-internals" }

lucet-wasi/src/main.rs

+70-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
extern crate clap;
33

44
use clap::Arg;
5+
use human_size::{Byte, Size};
56
use lucet_runtime::{self, DlModule, Limits, MmapRegion, Module, Region};
7+
use lucet_runtime_internals::module::ModuleInternal;
68
use lucet_wasi::{hostcalls, WasiCtxBuilder};
79
use std::fs::File;
810
use std::sync::Arc;
@@ -12,6 +14,7 @@ struct Config<'a> {
1214
guest_args: Vec<&'a str>,
1315
entrypoint: &'a str,
1416
preopen_dirs: Vec<(File, &'a str)>,
17+
limits: Limits,
1518
}
1619

1720
fn main() {
@@ -41,10 +44,10 @@ fn main() {
4144
virtual filesystem. Each directory is specified as \
4245
--dir `host_path:guest_path`, where `guest_path` specifies the path that will \
4346
correspond to `host_path` for calls like `fopen` in the guest.\
44-
\
47+
\n\n\
4548
For example, `--dir /home/host_user/wasi_sandbox:/sandbox` will make \
4649
`/home/host_user/wasi_sandbox` available within the guest as `/sandbox`.\
47-
\
50+
\n\n\
4851
Guests will be able to access any files and directories under the \
4952
`host_path`, but will be unable to access other parts of the host \
5053
filesystem through relative paths (e.g., `/sandbox/../some_other_file`) \
@@ -56,6 +59,27 @@ fn main() {
5659
.required(true)
5760
.help("Path to the `lucetc`-compiled WASI module"),
5861
)
62+
.arg(
63+
Arg::with_name("heap_memory_size")
64+
.long("max-heap-size")
65+
.takes_value(true)
66+
.default_value("4 GiB")
67+
.help("Maximum heap size (must be a multiple of 4 KiB)"),
68+
)
69+
.arg(
70+
Arg::with_name("heap_address_space_size")
71+
.long("heap-address-space")
72+
.takes_value(true)
73+
.default_value("8 GiB")
74+
.help("Maximum heap address space size (must be a multiple of 4 KiB, and >= `max-heap-size`)"),
75+
)
76+
.arg(
77+
Arg::with_name("stack_size")
78+
.long("stack-size")
79+
.takes_value(true)
80+
.default_value("8 MiB")
81+
.help("Maximum stack size (must be a multiple of 4 KiB)"),
82+
)
5983
.arg(
6084
Arg::with_name("guest_args")
6185
.required(false)
@@ -65,7 +89,9 @@ fn main() {
6589
.get_matches();
6690

6791
let entrypoint = matches.value_of("entrypoint").unwrap();
92+
6893
let lucet_module = matches.value_of("lucet_module").unwrap();
94+
6995
let preopen_dirs = matches
7096
.values_of("preopen_dirs")
7197
.map(|vals| {
@@ -84,25 +110,66 @@ fn main() {
84110
.collect()
85111
})
86112
.unwrap_or(vec![]);
113+
114+
let heap_memory_size = value_t!(matches, "heap_memory_size", Size)
115+
.unwrap_or_else(|e| e.exit())
116+
.into::<Byte>()
117+
.value() as usize;
118+
let heap_address_space_size = value_t!(matches, "heap_address_space_size", Size)
119+
.unwrap_or_else(|e| e.exit())
120+
.into::<Byte>()
121+
.value() as usize;
122+
123+
if heap_memory_size > heap_address_space_size {
124+
println!("`heap-address-space` must be at least as large as `max-heap-size`");
125+
println!("{}", matches.usage());
126+
std::process::exit(1);
127+
}
128+
129+
let stack_size = value_t!(matches, "stack_size", Size)
130+
.unwrap_or_else(|e| e.exit())
131+
.into::<Byte>()
132+
.value() as usize;
133+
134+
let limits = Limits {
135+
heap_memory_size,
136+
heap_address_space_size,
137+
stack_size,
138+
globals_size: 0, // calculated from module
139+
};
140+
87141
let guest_args = matches
88142
.values_of("guest_args")
89143
.map(|vals| vals.collect())
90144
.unwrap_or(vec![]);
145+
91146
let config = Config {
92147
lucet_module,
93148
guest_args,
94149
entrypoint,
95150
preopen_dirs,
151+
limits,
96152
};
153+
97154
run(config)
98155
}
99156

100157
fn run(config: Config) {
101158
lucet_wasi::hostcalls::ensure_linked();
102159
let exitcode = {
103160
// doing all of this in a block makes sure everything gets dropped before exiting
104-
let region = MmapRegion::create(1, &Limits::default()).expect("region can be created");
105161
let module = DlModule::load(&config.lucet_module).expect("module can be loaded");
162+
let min_globals_size = module.globals().len() * std::mem::size_of::<u64>();
163+
let globals_size = ((min_globals_size + 4096 - 1) / 4096) * 4096;
164+
165+
let region = MmapRegion::create(
166+
1,
167+
&Limits {
168+
globals_size,
169+
..config.limits
170+
},
171+
)
172+
.expect("region can be created");
106173

107174
// put the path to the module on the front for argv[0]
108175
let args = std::iter::once(config.lucet_module)

lucetc/src/compiler/data.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub fn compile_sparse_page_data(compiler: &mut Compiler) -> Result<(), Error> {
7777
use crate::program::data::sparse::OwnedSparseData;
7878
let owned_data = OwnedSparseData::new(
7979
&compiler.prog.data_initializers()?,
80-
compiler.prog.heap_spec(),
80+
compiler.prog.heap_spec()?,
8181
);
8282
let sparse_data = owned_data.sparse_data();
8383

lucetc/src/compiler/entity/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl<'p> EntityCreator<'p> {
7676
compiler: &Compiler,
7777
) -> Result<ir::Heap, Error> {
7878
let base = self.bases.heap(func, compiler);
79-
let heap_spec = self.program.heap_spec();
79+
let heap_spec = self.program.heap_spec()?;
8080

8181
self.cache.heap(index, || {
8282
if index != 0 {

lucetc/src/compiler/memory.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use cranelift_module::{DataContext, Linkage};
55
use failure::Error;
66

77
pub fn compile_memory_specs(compiler: &mut Compiler) -> Result<(), Error> {
8-
let heap = compiler.prog.heap_spec();
8+
let heap = compiler.prog.heap_spec()?;
99

1010
let mut heap_spec_ctx = DataContext::new();
1111
heap_spec_ctx.define(serialize_spec(&heap).into_boxed_slice());

lucetc/src/compiler/module_data.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ use lucet_module_data::ModuleData;
77

88
pub fn compile_module_data(compiler: &mut Compiler) -> Result<(), Error> {
99
let module_data_serialized: Vec<u8> = {
10-
let heap_spec = compiler.prog.heap_spec();
10+
let heap_spec = compiler.prog.heap_spec()?;
1111
let compiled_data = OwnedSparseData::new(
1212
&compiler.prog.data_initializers()?,
13-
compiler.prog.heap_spec(),
13+
compiler.prog.heap_spec()?,
1414
);
1515
let sparse_data = compiled_data.sparse_data();
1616

lucetc/src/lib.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,20 @@ impl Lucetc {
8787
Ok(())
8888
}
8989

90-
pub fn reserved_size(mut self, reserved_size: u64) -> Self {
91-
self.with_reserved_size(reserved_size);
90+
pub fn min_reserved_size(mut self, min_reserved_size: u64) -> Self {
91+
self.with_min_reserved_size(min_reserved_size);
9292
self
9393
}
94-
pub fn with_reserved_size(&mut self, reserved_size: u64) {
95-
self.heap.reserved_size = reserved_size;
94+
pub fn with_min_reserved_size(&mut self, min_reserved_size: u64) {
95+
self.heap.min_reserved_size = min_reserved_size;
96+
}
97+
98+
pub fn max_reserved_size(mut self, max_reserved_size: u64) -> Self {
99+
self.with_max_reserved_size(max_reserved_size);
100+
self
101+
}
102+
pub fn with_max_reserved_size(&mut self, max_reserved_size: u64) {
103+
self.heap.max_reserved_size = max_reserved_size;
96104
}
97105

98106
pub fn guard_size(mut self, guard_size: u64) -> Self {

lucetc/src/main.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ pub fn run(opts: &Options) -> Result<(), Error> {
5050
c.with_builtins(builtins)?;
5151
}
5252

53-
if let Some(reserved_size) = opts.reserved_size {
54-
c.with_reserved_size(reserved_size);
53+
if let Some(min_reserved_size) = opts.min_reserved_size {
54+
c.with_min_reserved_size(min_reserved_size);
55+
}
56+
57+
if let Some(max_reserved_size) = opts.max_reserved_size {
58+
c.with_max_reserved_size(max_reserved_size);
5559
}
5660

5761
if let Some(guard_size) = opts.guard_size {

0 commit comments

Comments
 (0)