Skip to content
This repository was archived by the owner on Oct 13, 2023. It is now read-only.

Commit 45bc253

Browse files
authored
Implement args_{get,sizes_get} functions (#16)
This commit updates the implementation of `cabi_export_realloc` to allocate from a bump-allocated-region in `State` rather than allocating a separate page for each argument as previously done. Additionally the argument data is now stored within `State` as well enabling a full implementation of the `args_get` and `args_sizes_get` syscalls.
1 parent 04e31f3 commit 45bc253

File tree

3 files changed

+141
-34
lines changed

3 files changed

+141
-34
lines changed

host/tests/runtime.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,13 @@ fn run_panic(mut store: Store<WasiCtx>, wasi: Wasi) -> Result<()> {
5555
println!("{:?}", r);
5656
Ok(())
5757
}
58+
59+
fn run_args(mut store: Store<WasiCtx>, wasi: Wasi) -> Result<()> {
60+
wasi.command(
61+
&mut store,
62+
0 as host::Descriptor,
63+
1 as host::Descriptor,
64+
&["hello", "this", "", "is an argument", "with 🚩 emoji"],
65+
)?;
66+
Ok(())
67+
}

src/lib.rs

Lines changed: 124 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,23 @@ mod bindings {
2323
}
2424

2525
#[export_name = "command"]
26-
unsafe extern "C" fn command_entrypoint(stdin: i32, stdout: i32, _args_ptr: i32, _args_len: i32) {
26+
unsafe extern "C" fn command_entrypoint(
27+
stdin: u32,
28+
stdout: u32,
29+
args_ptr: *const WasmStr,
30+
args_len: usize,
31+
) {
2732
State::with_mut(|state| {
2833
state.push_desc(Descriptor::File(File {
29-
fd: stdin as u32,
34+
fd: stdin,
3035
position: Cell::new(0),
3136
}))?;
3237
state.push_desc(Descriptor::File(File {
33-
fd: stdout as u32,
38+
fd: stdout,
3439
position: Cell::new(0),
3540
}))?;
3641
state.push_desc(Descriptor::Log)?;
42+
state.args = Some(slice::from_raw_parts(args_ptr, args_len));
3743
Ok(())
3844
});
3945

@@ -84,49 +90,83 @@ pub unsafe extern "C" fn cabi_import_realloc(
8490
ptr
8591
}
8692

93+
/// This allocator is only used for the `command` entrypoint.
94+
///
95+
/// The implementation here is a bump allocator into `State::command_data` which
96+
/// traps when it runs out of data. This means that the total size of
97+
/// arguments/env/etc coming into a component is bounded by the current 64k
98+
/// (ish) limit. That's just an implementation limit though which can be lifted
99+
/// by dynamically calling `memory.grow` as necessary for more data.
87100
#[no_mangle]
88101
pub unsafe extern "C" fn cabi_export_realloc(
89102
old_ptr: *mut u8,
90103
old_size: usize,
91104
align: usize,
92105
new_size: usize,
93106
) -> *mut u8 {
94-
if !old_ptr.is_null() {
95-
unreachable();
96-
}
97-
if new_size > PAGE_SIZE {
98-
unreachable();
99-
}
100-
let grew = core::arch::wasm32::memory_grow(0, 1);
101-
if grew == usize::MAX {
107+
if !old_ptr.is_null() || old_size != 0 {
102108
unreachable();
103109
}
104-
(grew * PAGE_SIZE) as *mut u8
110+
let mut ret = null_mut::<u8>();
111+
State::with_mut(|state| {
112+
let data = state.command_data.as_mut_ptr();
113+
let ptr = usize::try_from(state.command_data_next).unwrap();
114+
115+
// "oom" as too much argument data tried to flow into the component.
116+
// Ideally this would have a better error message?
117+
if ptr + new_size > (*data).len() {
118+
unreachable();
119+
}
120+
state.command_data_next += new_size as u16;
121+
ret = (*data).as_mut_ptr().add(ptr);
122+
Ok(())
123+
});
124+
ret
105125
}
106126

107127
/// Read command-line argument data.
108128
/// The size of the array should match that returned by `args_sizes_get`
109129
#[no_mangle]
110-
pub unsafe extern "C" fn args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno {
111-
// TODO: Use real arguments.
112-
// Store bytes one at a time to avoid needing a static init.
113-
argv_buf.add(0).write(b'w');
114-
argv_buf.add(1).write(b'a');
115-
argv_buf.add(2).write(b's');
116-
argv_buf.add(3).write(b'm');
117-
argv_buf.add(4).write(b'\0');
118-
argv.add(0).write(argv_buf);
119-
argv.add(1).write(null_mut());
120-
ERRNO_SUCCESS
130+
pub unsafe extern "C" fn args_get(mut argv: *mut *mut u8, mut argv_buf: *mut u8) -> Errno {
131+
State::with(|state| {
132+
if let Some(args) = state.args {
133+
for arg in args {
134+
// Copy the argument into `argv_buf` which must be sized
135+
// appropriately by the caller.
136+
ptr::copy_nonoverlapping(arg.ptr, argv_buf, arg.len);
137+
*argv_buf.add(arg.len) = 0;
138+
139+
// Copy the argument pointer into the `argv` buf
140+
*argv = argv_buf;
141+
142+
// Update our pointers past what's written to prepare for the
143+
// next argument.
144+
argv = argv.add(1);
145+
argv_buf = argv_buf.add(arg.len + 1);
146+
}
147+
}
148+
Ok(())
149+
})
121150
}
122151

123152
/// Return command-line argument data sizes.
124153
#[no_mangle]
125154
pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno {
126-
// TODO: Use real arguments.
127-
*argc = 1;
128-
*argv_buf_size = 5;
129-
ERRNO_SUCCESS
155+
State::with(|state| {
156+
match state.args {
157+
Some(args) => {
158+
*argc = args.len();
159+
// Add one to each length for the terminating nul byte added by
160+
// the `args_get` function.
161+
*argv_buf_size = args.iter().map(|s| s.len + 1).sum();
162+
}
163+
None => {
164+
*argc = 0;
165+
*argv_buf_size = 0;
166+
}
167+
}
168+
Ok(())
169+
})
130170
}
131171

132172
/// Read environment variable data.
@@ -1153,14 +1193,61 @@ const PAGE_SIZE: usize = 65536;
11531193
/// polyfill.
11541194
const PATH_MAX: usize = 4096;
11551195

1196+
const MAX_DESCRIPTORS: usize = 128;
1197+
11561198
struct State {
1199+
/// Used by `register_buffer` to coordinate allocations with
1200+
/// `cabi_import_realloc`.
11571201
buffer_ptr: Cell<*mut u8>,
11581202
buffer_len: Cell<usize>,
1159-
ndescriptors: usize,
1160-
descriptors: MaybeUninit<[Descriptor; 128]>,
1203+
1204+
/// Storage of mapping from preview1 file descriptors to preview2 file
1205+
/// descriptors.
1206+
ndescriptors: u16,
1207+
descriptors: MaybeUninit<[Descriptor; MAX_DESCRIPTORS]>,
1208+
1209+
/// Auxiliary storage to handle the `path_readlink` function.
11611210
path_buf: UnsafeCell<MaybeUninit<[u8; PATH_MAX]>>,
1211+
1212+
/// Storage area for data passed to the `command` entrypoint. The
1213+
/// `command_data` is a block of memory which is dynamically allocated from
1214+
/// in `cabi_export_realloc`. The `command_data_next` is the
1215+
/// bump-allocated-pointer of where to allocate from next.
1216+
command_data: MaybeUninit<[u8; command_data_size()]>,
1217+
command_data_next: u16,
1218+
1219+
/// Arguments passed to the `command` entrypoint
1220+
args: Option<&'static [WasmStr]>,
11621221
}
11631222

1223+
struct WasmStr {
1224+
ptr: *const u8,
1225+
len: usize,
1226+
}
1227+
1228+
const fn command_data_size() -> usize {
1229+
// The total size of the struct should be a page, so start there
1230+
let mut start = PAGE_SIZE;
1231+
1232+
// Remove the big chunks of the struct, the `path_buf` and `descriptors`
1233+
// fields.
1234+
start -= PATH_MAX;
1235+
start -= size_of::<Descriptor>() * MAX_DESCRIPTORS;
1236+
1237+
// Remove miscellaneous metadata also stored in state.
1238+
start -= 5 * size_of::<usize>();
1239+
1240+
// Everything else is the `command_data` allocation.
1241+
start
1242+
}
1243+
1244+
// Statically assert that the `State` structure is the size of a wasm page. This
1245+
// mostly guarantees that it's not larger than one page which is relied upon
1246+
// below.
1247+
const _: () = {
1248+
let _size_assert: [(); PAGE_SIZE] = [(); size_of::<State>()];
1249+
};
1250+
11641251
#[allow(improper_ctypes)]
11651252
extern "C" {
11661253
fn get_global_ptr() -> *const RefCell<State>;
@@ -1189,7 +1276,6 @@ impl State {
11891276
}
11901277

11911278
fn ptr() -> &'static RefCell<State> {
1192-
assert!(size_of::<State>() <= PAGE_SIZE);
11931279
unsafe {
11941280
let mut ptr = get_global_ptr();
11951281
if ptr.is_null() {
@@ -1214,6 +1300,9 @@ impl State {
12141300
ndescriptors: 0,
12151301
descriptors: MaybeUninit::uninit(),
12161302
path_buf: UnsafeCell::new(MaybeUninit::uninit()),
1303+
command_data: MaybeUninit::uninit(),
1304+
command_data_next: 0,
1305+
args: None,
12171306
}));
12181307
&*ret
12191308
}
@@ -1222,18 +1311,19 @@ impl State {
12221311
fn push_desc(&mut self, desc: Descriptor) -> Result<Fd, Errno> {
12231312
unsafe {
12241313
let descriptors = self.descriptors.as_mut_ptr();
1225-
if self.ndescriptors >= (*descriptors).len() {
1314+
let ndescriptors = usize::try_from(self.ndescriptors).unwrap();
1315+
if ndescriptors >= (*descriptors).len() {
12261316
return Err(ERRNO_INVAL);
12271317
}
1228-
ptr::addr_of_mut!((*descriptors)[self.ndescriptors]).write(desc);
1318+
ptr::addr_of_mut!((*descriptors)[ndescriptors]).write(desc);
12291319
self.ndescriptors += 1;
1230-
Ok(Fd::try_from(self.ndescriptors - 1).unwrap())
1320+
Ok(Fd::from(self.ndescriptors - 1))
12311321
}
12321322
}
12331323

12341324
fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> {
12351325
let index = usize::try_from(fd).unwrap();
1236-
if index < self.ndescriptors {
1326+
if index < usize::try_from(self.ndescriptors).unwrap() {
12371327
unsafe { (*self.descriptors.as_ptr()).get(index).ok_or(ERRNO_BADF) }
12381328
} else {
12391329
Err(ERRNO_BADF)

test-programs/src/bin/args.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fn main() {
2+
let args = std::env::args().collect::<Vec<_>>();
3+
assert_eq!(
4+
args,
5+
["hello", "this", "", "is an argument", "with 🚩 emoji"]
6+
);
7+
}

0 commit comments

Comments
 (0)