Skip to content

Commit 13637c0

Browse files
authored
wasip2: Fix/optimize read/write family of syscalls (WebAssembly#670)
This commit contains a number of fixes to `{p,}{read,write}{,v}` functions, notably: * For `write`-style functions the buffer passed in need not be copied to a heap allocation. Instead it can be used as-is in-place. * `for `*v`-style functions it's not valid to perform operations in a loop. The functions are documented as having atomic semantics (e.g. `writev` writes everything as one "chunk") which means that the one-write-behavior of the adapter is required. * `preadv` no longer leaks memory for >1 buffer passed in. * `pwritev` is implemented in terms of `pwrite` to reduce duplication. * `writev` only performs one write instead of performing multiple and not handling errors in the middle. * For `read`-style functions the return value of `memcpy` need not be checked as the function is infallible. * The `write` function uses the return value from `check-write` to determine the length of the write instead of discarding it. * The download of a precompiled wasmtime was updated to support macOS as well.
1 parent ae64b6f commit 13637c0

File tree

9 files changed

+111
-138
lines changed

9 files changed

+111
-138
lines changed

libc-bottom-half/cloudlibc/src/libc/sys/uio/preadv.c

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <wasi/wasip2.h>
99
#include <wasi/file_utils.h>
1010
#include <common/errors.h>
11+
#include <unistd.h>
1112
#else
1213
#include <wasi/api.h>
1314
#endif
@@ -20,48 +21,16 @@ ssize_t preadv(int fildes, const struct iovec *iov, int iovcnt, off_t offset) {
2021
}
2122
size_t bytes_read = 0;
2223
#ifdef __wasilibc_use_wasip2
23-
// Translate the file descriptor to an internal handle
24-
filesystem_borrow_descriptor_t file_handle;
25-
if (fd_to_file_handle(fildes, &file_handle) < 0)
26-
return -1;
27-
28-
// Create a WASI buffer to receive the contents
29-
wasip2_tuple2_list_u8_bool_t buffer;
30-
filesystem_error_code_t error_code;
31-
bool ok = true;
32-
bool free_buffer = false;
33-
34-
// Differently from the behavior of the preview1 component adapter,
35-
// read into all non-empty iovecs
36-
for (size_t i = 0; i < iovcnt; i++) {
37-
if (iov[i].iov_len == 0)
38-
continue;
39-
40-
// Read the data
41-
ok = filesystem_method_descriptor_read(file_handle,
42-
iov[i].iov_len,
43-
offset,
44-
&buffer,
45-
&error_code);
46-
free_buffer = true;
47-
if (buffer.f0.len > INT32_MAX) {
48-
// In this case, the number of bytes read can't
49-
// be represented by an ssize_t
50-
errno = EINVAL;
51-
wasip2_list_u8_free(&buffer.f0);
52-
return -1;
24+
// Skip empty iovecs and then delegate to `pread` with the first non-empty
25+
// iovec.
26+
while (iovcnt) {
27+
if (iov->iov_len != 0) {
28+
return pread(fildes, iov->iov_base, iov->iov_len, offset);
5329
}
54-
bytes_read += buffer.f0.len;
55-
// Copy the contents of the buffer into the iov
56-
memcpy(iov[i].iov_base, buffer.f0.ptr, buffer.f0.len);
57-
}
58-
if (free_buffer)
59-
wasip2_list_u8_free(&buffer.f0);
60-
61-
if (!ok) {
62-
translate_error(error_code);
63-
return -1;
30+
iovcnt--;
31+
iov++;
6432
}
33+
return pread(fildes, NULL, 0, offset);
6534
#else
6635
__wasi_errno_t error = __wasi_fd_pread(
6736
fildes, (const __wasi_iovec_t *)iov, iovcnt, offset, &bytes_read);

libc-bottom-half/cloudlibc/src/libc/sys/uio/pwritev.c

Lines changed: 12 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <wasi/wasip2.h>
99
#include <wasi/file_utils.h>
1010
#include <common/errors.h>
11+
#include <unistd.h>
1112
#else
1213
#include <wasi/api.h>
1314
#endif
@@ -18,62 +19,25 @@ ssize_t pwritev(int fildes, const struct iovec *iov, int iovcnt, off_t offset) {
1819
errno = EINVAL;
1920
return -1;
2021
}
21-
size_t bytes_written;
2222
#ifdef __wasilibc_use_wasip2
23-
// Note: following the preview1 adapter's behavior, only the first non-empty
24-
// iov is written.
25-
26-
// Find the first non-empty iov
27-
int32_t i = 0;
28-
while (i < iovcnt) {
29-
if (iov[i].iov_len != 0)
30-
break;
31-
i++;
32-
}
33-
34-
// If there is none, return
35-
if (i >= iovcnt) {
36-
return 0;
37-
}
38-
39-
// Translate the file descriptor to an internal handle
40-
filesystem_borrow_descriptor_t file_handle;
41-
if (fd_to_file_handle(fildes, &file_handle) < 0)
42-
return -1;
43-
44-
// Create a WASI buffer to copy the contents into
45-
wasip2_list_u8_t buffer;
46-
buffer.len = iov[i].iov_len;
47-
buffer.ptr = malloc(buffer.len);
48-
memcpy(buffer.ptr, iov[i].iov_base, buffer.len);
49-
50-
// Write the data
51-
filesystem_error_code_t error_code;
52-
filesystem_filesize_t num_bytes;
53-
bool ok = filesystem_method_descriptor_write(file_handle,
54-
&buffer,
55-
offset,
56-
&num_bytes,
57-
&error_code);
58-
wasip2_list_u8_free(&buffer);
59-
if (!ok) {
60-
translate_error(error_code);
61-
return -1;
62-
}
63-
if (num_bytes > INT32_MAX) {
64-
// In this case, the number of bytes written can't
65-
// be represented by an ssize_t
66-
errno = EINVAL;
67-
return -1;
23+
// Skip empty iovecs and then delegate to `pwrite` with the first non-empty
24+
// iovec.
25+
while (iovcnt) {
26+
if (iov->iov_len != 0) {
27+
return pwrite(fildes, iov->iov_base, iov->iov_len, offset);
28+
}
29+
iovcnt--;
30+
iov++;
6831
}
69-
bytes_written = (ssize_t) num_bytes;
32+
return pwrite(fildes, NULL, 0, offset);
7033
#else
34+
size_t bytes_written;
7135
__wasi_errno_t error = __wasi_fd_pwrite(
7236
fildes, (const __wasi_ciovec_t *)iov, iovcnt, offset, &bytes_written);
7337
if (error != 0) {
7438
errno = error;
7539
return -1;
7640
}
77-
#endif
7841
return bytes_written;
42+
#endif
7943
}

libc-bottom-half/cloudlibc/src/libc/sys/uio/writev.c

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,26 @@ ssize_t writev(int fildes, const struct iovec *iov, int iovcnt) {
3636
errno = EINVAL;
3737
return -1;
3838
}
39-
size_t bytes_written = 0;
4039

4140
#ifdef __wasilibc_use_wasip2
42-
// Differently from the behavior of the preview1 component adapter,
43-
// write all non-empty iovecs
44-
45-
for (size_t i = 0; i < iovcnt; i++) {
46-
if (iov[i].iov_len == 0)
47-
continue;
48-
bytes_written += write(fildes, iov[i].iov_base, iov[i].iov_len);
41+
// Skip empty iovecs and then delegate to `read` with the first non-empty
42+
// iovec.
43+
while (iovcnt) {
44+
if (iov->iov_len != 0) {
45+
return write(fildes, iov->iov_base, iov->iov_len);
46+
}
47+
iovcnt--;
48+
iov++;
4949
}
50+
return write(fildes, NULL, 0);
5051
#else
52+
size_t bytes_written = 0;
5153
__wasi_errno_t error = __wasi_fd_write(
5254
fildes, (const __wasi_ciovec_t *)iov, iovcnt, &bytes_written);
5355
if (error != 0) {
5456
errno = error;
5557
return -1;
5658
}
57-
#endif
5859
return bytes_written;
60+
#endif
5961
}

libc-bottom-half/cloudlibc/src/libc/unistd/pread.c

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,8 @@ ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset) {
3636
&contents,
3737
&error_code);
3838
bytes_read = contents.f0.len;
39-
// Copy the result into the buffer
40-
if (!memcpy(buf, contents.f0.ptr, bytes_read)) {
41-
errno = EINVAL;
42-
return -1;
43-
}
44-
45-
// Free the list in the tuple
39+
// Copy the bytes allocated in the canonical ABI to `buf`
40+
memcpy(buf, contents.f0.ptr, bytes_read);
4641
wasip2_list_u8_free(&contents.f0);
4742

4843
// Check for errors

libc-bottom-half/cloudlibc/src/libc/unistd/pwrite.c

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,7 @@ ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset) {
2727
// Convert `buf` to a WASI byte list
2828
wasip2_list_u8_t contents;
2929
contents.len = nbyte;
30-
contents.ptr = malloc(nbyte);
31-
if (!memcpy(contents.ptr, buf, nbyte)) {
32-
errno = EINVAL;
33-
return -1;
34-
}
30+
contents.ptr = (uint8_t*) buf;
3531

3632
// Write the bytes
3733
filesystem_filesize_t bytes_written;
@@ -41,9 +37,6 @@ ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset) {
4137
offset,
4238
&bytes_written,
4339
&error_code);
44-
// Free the byte list
45-
wasip2_list_u8_free(&contents);
46-
4740
// Check for errors
4841
if (!ok) {
4942
translate_error(error_code);

libc-bottom-half/cloudlibc/src/libc/unistd/read.c

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,8 @@ ssize_t read(int fildes, void *buf, size_t nbyte) {
4242
}
4343
}
4444

45-
// Copy the bytes to `buf` so we can free the list
46-
if (!memcpy(buf, contents.ptr, contents.len)) {
47-
errno = EINVAL;
48-
return -1;
49-
}
45+
// Copy the bytes allocated in the canonical ABI to `buf`
46+
memcpy(buf, contents.ptr, contents.len);
5047
wasip2_list_u8_free(&contents);
5148

5249
// Update the offset

libc-bottom-half/cloudlibc/src/libc/unistd/write.c

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,32 +30,28 @@ ssize_t write(int fildes, const void *buf, size_t nbyte) {
3030
// Check readiness for writing
3131
uint64_t num_bytes_permitted = 0;
3232
streams_stream_error_t stream_error;
33-
ok = streams_method_output_stream_check_write(output_stream,
34-
&num_bytes_permitted,
35-
&stream_error);
36-
if (!ok) {
37-
errno = EIO;
38-
return -1;
39-
}
33+
while (num_bytes_permitted == 0) {
34+
ok = streams_method_output_stream_check_write(output_stream,
35+
&num_bytes_permitted,
36+
&stream_error);
37+
if (!ok) {
38+
errno = EIO;
39+
return -1;
40+
}
4041

41-
if (num_bytes_permitted < nbyte)
42-
poll_method_pollable_block(pollable);
42+
if (num_bytes_permitted == 0)
43+
poll_method_pollable_block(pollable);
44+
}
4345

4446
// Convert the buffer to a WASI list of bytes
4547
wasip2_list_u8_t contents;
46-
contents.len = nbyte;
47-
contents.ptr = malloc(nbyte);
48-
if (!memcpy(contents.ptr, buf, nbyte)) {
49-
errno = EINVAL;
50-
return -1;
51-
}
48+
contents.len = num_bytes_permitted < nbyte ? num_bytes_permitted : nbyte;
49+
contents.ptr = (uint8_t*) buf;
5250

5351
// Write the bytes to the stream
5452
ok = streams_method_output_stream_write(output_stream,
5553
&contents,
5654
&stream_error);
57-
wasip2_list_u8_free(&contents);
58-
5955
if (!ok) {
6056
errno = EIO;
6157
return -1;
@@ -69,8 +65,8 @@ ssize_t write(int fildes, const void *buf, size_t nbyte) {
6965
}
7066

7167
if (off)
72-
*off += nbyte;
73-
return nbyte;
68+
*off += contents.len;
69+
return contents.len;
7470
#else
7571
__wasi_ciovec_t iov = {.buf = buf, .buf_len = nbyte};
7672
size_t bytes_written;

test/CMakeLists.txt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,27 @@ message(STATUS "libc-test source directory: ${LIBC_TEST}")
5050

5151
if(NOT ENGINE OR ENGINE STREQUAL "")
5252
set(WASMTIME_VERSION "v38.0.2")
53-
set(WASMTIME_ARCH "${CMAKE_HOST_SYSTEM_PROCESSOR}")
5453
set(WASMTIME_REPO "https://github.com/bytecodealliance/wasmtime")
5554

55+
if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "x86_64")
56+
set(WASMTIME_ARCH "x86_64")
57+
elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64")
58+
set(WASMTIME_ARCH "aarch64")
59+
else()
60+
message(FATAL_ERROR "Unsupported architecture ${CMAKE_HOST_SYSTEM_PROCESSOR} for Wasmtime")
61+
endif()
62+
63+
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
64+
set(WASMTIME_OS macos)
65+
elseif (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
66+
set(WASMTIME_OS linux)
67+
else()
68+
message(FATAL_ERROR "Unsupported system ${CMAKE_SYSTEM_NAME} for Wasmtime")
69+
endif()
70+
5671
ExternalProject_Add(
5772
engine
58-
URL "${WASMTIME_REPO}/releases/download/${WASMTIME_VERSION}/wasmtime-${WASMTIME_VERSION}-${WASMTIME_ARCH}-linux.tar.xz"
73+
URL "${WASMTIME_REPO}/releases/download/${WASMTIME_VERSION}/wasmtime-${WASMTIME_VERSION}-${WASMTIME_ARCH}-${WASMTIME_OS}.tar.xz"
5974
CONFIGURE_COMMAND ""
6075
BUILD_COMMAND ""
6176
INSTALL_COMMAND ""
@@ -314,6 +329,7 @@ add_wasilibc_test(utime.c FS)
314329
add_wasilibc_test(rewinddir.c FS)
315330
add_wasilibc_test(seekdir.c FS)
316331
add_wasilibc_test(usleep.c)
332+
add_wasilibc_test(write.c FS)
317333

318334
if (TARGET_TRIPLE MATCHES "-threads")
319335
add_wasilibc_test(busywait.c)

test/src/write.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include <errno.h>
2+
#include <fcntl.h>
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
#include <string.h>
6+
#include <unistd.h>
7+
#include <sys/stat.h>
8+
#include <sys/uio.h>
9+
#include "test.h"
10+
11+
#define TEST(c) do { \
12+
errno = 0; \
13+
if (!(c)) \
14+
t_error("%s failed (errno = %d)\n", #c, errno); \
15+
} while(0)
16+
17+
#define N (100 << 20)
18+
19+
int main(void) {
20+
char tmp[] = "testsuite-XXXXXX";
21+
int fd;
22+
ssize_t r;
23+
24+
TEST((fd = open(tmp, O_RDWR | O_CREAT | O_EXCL, 0600)) > 2);
25+
26+
// write a big file of zeros, looping until it's fully written.
27+
char *bytes = calloc(1, N);
28+
TEST(bytes != NULL);
29+
size_t written = 0;
30+
while (written < N) {
31+
TEST((r = write(fd, bytes + written, N - written)) > 0);
32+
written += r;
33+
}
34+
TEST(close(fd) == 0);
35+
36+
struct stat buf;
37+
TEST(stat(tmp, &buf) == 0);
38+
TEST(buf.st_size == N);
39+
40+
return t_status;
41+
}

0 commit comments

Comments
 (0)