From a229e8398b910636b879ecc5a1aa6f30781de354 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 10 Oct 2024 17:37:27 -0700 Subject: [PATCH 1/4] Use error code for unsupported WASI clock ID --- .../main/java/com/dylibso/chicory/wasi/WasiPreview1.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java b/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java index 7cb9c2350..31f8bb5ca 100644 --- a/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java +++ b/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java @@ -181,9 +181,8 @@ public int clockResGet(Memory memory, int clockId, int resultPtr) { memory.writeLong(resultPtr, 1L); return wasiResult(WasiErrno.ESUCCESS); case WasiClockId.PROCESS_CPUTIME_ID: - throw new WASMRuntimeException("We don't yet support clockid process_cputime_id"); case WasiClockId.THREAD_CPUTIME_ID: - throw new WASMRuntimeException("We don't yet support clockid thread_cputime_id"); + return wasiResult(WasiErrno.ENOTSUP); default: return wasiResult(WasiErrno.EINVAL); } @@ -202,9 +201,8 @@ public int clockTimeGet(Memory memory, int clockId, long precision, int resultPt memory.writeLong(resultPtr, System.nanoTime()); return wasiResult(WasiErrno.ESUCCESS); case WasiClockId.PROCESS_CPUTIME_ID: - throw new WASMRuntimeException("We don't yet support clockid process_cputime_id"); case WasiClockId.THREAD_CPUTIME_ID: - throw new WASMRuntimeException("We don't yet support clockid thread_cputime_id"); + return wasiResult(WasiErrno.ENOTSUP); default: return wasiResult(WasiErrno.EINVAL); } From 1910a129e13cfc5b8d2ff42af7577071b5f0e73b Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 10 Oct 2024 13:09:49 -0700 Subject: [PATCH 2/4] Use FileChannel in WASI --- .../main/java/com/dylibso/chicory/wasi/Descriptors.java | 8 ++++---- .../main/java/com/dylibso/chicory/wasi/WasiPreview1.java | 8 ++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/wasi/src/main/java/com/dylibso/chicory/wasi/Descriptors.java b/wasi/src/main/java/com/dylibso/chicory/wasi/Descriptors.java index e5fdb1b64..878654149 100644 --- a/wasi/src/main/java/com/dylibso/chicory/wasi/Descriptors.java +++ b/wasi/src/main/java/com/dylibso/chicory/wasi/Descriptors.java @@ -7,7 +7,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.nio.channels.SeekableByteChannel; +import java.nio.channels.FileChannel; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -140,11 +140,11 @@ public Path path() { static final class OpenFile implements Descriptor, Closeable, DataReader, DataWriter { private final Path path; - private final SeekableByteChannel channel; + private final FileChannel channel; private final int fdFlags; private final long rights; - public OpenFile(Path path, SeekableByteChannel channel, int fdFlags, long rights) { + public OpenFile(Path path, FileChannel channel, int fdFlags, long rights) { this.path = requireNonNull(path); this.channel = requireNonNull(channel); this.fdFlags = fdFlags; @@ -155,7 +155,7 @@ public Path path() { return path; } - public SeekableByteChannel channel() { + public FileChannel channel() { return channel; } diff --git a/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java b/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java index 31f8bb5ca..34af490d2 100644 --- a/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java +++ b/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java @@ -1046,7 +1046,7 @@ public int pathOpen( int fd; try { - SeekableByteChannel channel = Files.newByteChannel(path, openOptions); + FileChannel channel = FileChannel.open(path, openOptions); fd = descriptors.allocate(new OpenFile(path, channel, fdFlags, rightsBase)); } catch (FileAlreadyExistsException e) { return wasiResult(WasiErrno.EEXIST); @@ -1347,13 +1347,9 @@ private WasiErrno fileSync(int fd, boolean metadata) { throw unhandledDescriptor(descriptor); } var channel = ((OpenFile) descriptor).channel(); - if (!(channel instanceof FileChannel)) { - return WasiErrno.ENOTSUP; - } - var fileChannel = (FileChannel) channel; try { - fileChannel.force(metadata); + channel.force(metadata); } catch (IOException e) { return WasiErrno.EIO; } From 767e930dbcf681bf5de94e451a0abb45e7621a3c Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 10 Oct 2024 14:09:47 -0700 Subject: [PATCH 3/4] Implement WASI fd_pread and fd_pwrite --- docs/docs/usage/wasi.md | 5 +- wasi/pom.xml | 7 +- .../com/dylibso/chicory/wasi/Descriptors.java | 8 ++ .../dylibso/chicory/wasi/WasiPreview1.java | 108 +++++++++++++++++- .../chicory/wasi/WasiPreview1Test.java | 46 ++++++++ 5 files changed, 161 insertions(+), 13 deletions(-) diff --git a/docs/docs/usage/wasi.md b/docs/docs/usage/wasi.md index cbc39adce..a4038bfa6 100644 --- a/docs/docs/usage/wasi.md +++ b/docs/docs/usage/wasi.md @@ -147,10 +147,10 @@ For the most up-to-date info, and to see what specific functions we support, see | fd_filestat_get | ✅ | | | fd_filestat_set_size | ✅ | | | fd_filestat_set_times | ✅ | | -| fd_pread | ❌ | | +| fd_pread | ✅ | | | fd_prestat_dir_name | ✅ | | | fd_prestat_get | ✅ | | -| fd_pwrite | ❌ | | +| fd_pwrite | 🟡 | Not supported for files opened in append mode. | | fd_read | ✅ | | | fd_readdir | ✅ | | | fd_renumber | ✅ | | @@ -177,4 +177,3 @@ For the most up-to-date info, and to see what specific functions we support, see | sock_recv | ❌ | | | sock_send | ❌ | | | sock_shutdown | ✅ | | - diff --git a/wasi/pom.xml b/wasi/pom.xml index c6c86fd91..f0ed7cd3a 100644 --- a/wasi/pom.xml +++ b/wasi/pom.xml @@ -76,12 +76,7 @@ tests/rust/testsuite/fd_flags_set.wasm - - tests/c/testsuite/pread-with-access.wasm - tests/rust/testsuite/dir_fd_op_failures.wasm - tests/rust/testsuite/file_pread_pwrite.wasm - - tests/c/testsuite/pwrite-with-access.wasm + tests/c/testsuite/pwrite-with-append.wasm tests/rust/testsuite/poll_oneoff_stdio.wasm diff --git a/wasi/src/main/java/com/dylibso/chicory/wasi/Descriptors.java b/wasi/src/main/java/com/dylibso/chicory/wasi/Descriptors.java index 878654149..2f7b35ae2 100644 --- a/wasi/src/main/java/com/dylibso/chicory/wasi/Descriptors.java +++ b/wasi/src/main/java/com/dylibso/chicory/wasi/Descriptors.java @@ -172,11 +172,19 @@ public int read(byte[] data) throws IOException { return channel.read(ByteBuffer.wrap(data)); } + public int read(byte[] data, long position) throws IOException { + return channel.read(ByteBuffer.wrap(data), position); + } + @Override public int write(byte[] data) throws IOException { return channel.write(ByteBuffer.wrap(data)); } + public int write(byte[] data, long position) throws IOException { + return channel.write(ByteBuffer.wrap(data), position); + } + @Override public void close() throws IOException { channel.close(); diff --git a/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java b/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java index 34af490d2..6421c4df9 100644 --- a/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java +++ b/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java @@ -519,9 +519,58 @@ public int fdFilestatSetTimes(int fd, long accessTime, long modifiedTime, int fs } @WasmExport - public int fdPread(int fd, int iovs, int iovsLen, long offset, int nreadPtr) { + public int fdPread(Memory memory, int fd, int iovs, int iovsLen, long offset, int nreadPtr) { logger.tracef("fd_pread: [%s, %s, %s, %s, %s]", fd, iovs, iovsLen, offset, nreadPtr); - throw new WASMRuntimeException("We don't yet support this WASI call: fd_pread"); + + if (offset < 0) { + return wasiResult(WasiErrno.EINVAL); + } + + var descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if (descriptor instanceof InStream) { + return wasiResult(WasiErrno.ESPIPE); + } + if (descriptor instanceof OutStream) { + return wasiResult(WasiErrno.EBADF); + } + if (descriptor instanceof Directory) { + return wasiResult(WasiErrno.EISDIR); + } + if (!(descriptor instanceof OpenFile)) { + throw unhandledDescriptor(descriptor); + } + var file = (OpenFile) descriptor; + + int totalRead = 0; + for (var i = 0; i < iovsLen; i++) { + int base = iovs + (i * 8); + int iovBase = memory.readInt(base); + var iovLen = memory.readInt(base + 4); + try { + byte[] data = new byte[iovLen]; + int read = file.read(data, offset); + if (read < 0) { + break; + } + memory.write(iovBase, data, 0, read); + offset += read; + totalRead += read; + if (read < iovLen) { + break; + } + } catch (NonReadableChannelException e) { + return wasiResult(WasiErrno.ENOTCAPABLE); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + } + + memory.writeI32(nreadPtr, totalRead); + return wasiResult(WasiErrno.ESUCCESS); } @WasmExport @@ -564,9 +613,60 @@ public int fdPrestatGet(Memory memory, int fd, int buf) { } @WasmExport - public int fdPwrite(int fd, int iovs, int iovsLen, long offset, int nwrittenPtr) { + public int fdPwrite( + Memory memory, int fd, int iovs, int iovsLen, long offset, int nwrittenPtr) { logger.tracef("fd_pwrite: [%s, %s, %s, %s, %s]", fd, iovs, iovsLen, offset, nwrittenPtr); - throw new WASMRuntimeException("We don't yet support this WASI call: fd_pwrite"); + + if (offset < 0) { + return wasiResult(WasiErrno.EINVAL); + } + + var descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if (descriptor instanceof InStream) { + return wasiResult(WasiErrno.EBADF); + } + if (descriptor instanceof OutStream) { + return wasiResult(WasiErrno.ESPIPE); + } + if (descriptor instanceof Directory) { + return wasiResult(WasiErrno.EISDIR); + } + + if (!(descriptor instanceof OpenFile)) { + throw unhandledDescriptor(descriptor); + } + var file = (OpenFile) descriptor; + + if (flagSet(file.fdFlags(), WasiFdFlags.APPEND)) { + return wasiResult(WasiErrno.ENOTSUP); + } + + var totalWritten = 0; + for (var i = 0; i < iovsLen; i++) { + var base = iovs + (i * 8); + var iovBase = memory.readInt(base); + var iovLen = memory.readInt(base + 4); + var data = memory.readBytes(iovBase, iovLen); + try { + int written = file.write(data, offset); + offset += written; + totalWritten += written; + if (written < iovLen) { + break; + } + } catch (NonWritableChannelException e) { + return wasiResult(WasiErrno.ENOTCAPABLE); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + } + + memory.writeI32(nwrittenPtr, totalWritten); + return wasiResult(WasiErrno.ESUCCESS); } @WasmExport diff --git a/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java b/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java index 55b265e71..15829b7ef 100644 --- a/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java +++ b/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java @@ -1,5 +1,6 @@ package com.dylibso.chicory.wasi; +import static com.dylibso.chicory.wasi.Files.copyDirectory; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -14,8 +15,14 @@ import com.dylibso.chicory.wasm.Module; import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.wasm.types.MemoryLimits; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Path; import java.util.List; import java.util.Random; import org.junit.jupiter.api.Test; @@ -226,4 +233,43 @@ public void wasiRandom() { assertArrayEquals(first, memory.readBytes(0, 123_456)); assertArrayEquals(second, memory.readBytes(222_222, 87_654)); } + + @Test + public void wasiPositionedWriteWithAppendShouldFail() throws IOException { + try (FileSystem fs = + Jimfs.newFileSystem( + Configuration.unix().toBuilder().setAttributeViews("unix").build())) { + + var dir = "fs-tests.dir"; + Path source = new File("../wasi-testsuite/tests/c/testsuite").toPath().resolve(dir); + Path target = fs.getPath(dir); + copyDirectory(source, target); + + var options = WasiOptions.builder().withDirectory(target.toString(), target).build(); + + try (var wasi = WasiPreview1.builder().withOpts(options).build()) { + var memory = new Memory(new MemoryLimits(1)); + + int fdPtr = 0; + int result = + wasi.pathOpen( + memory, + 3, + WasiLookupFlags.SYMLINK_FOLLOW, + "pwrite.cleanup", + WasiOpenFlags.CREAT, + 0, + 0, + WasiFdFlags.APPEND, + fdPtr); + assertEquals(WasiErrno.ESUCCESS.value(), result); + + int fd = memory.readInt(fdPtr); + assertEquals(4, fd); + + result = wasi.fdPwrite(memory, fd, 0, 0, 0, 0); + assertEquals(WasiErrno.ENOTSUP.value(), result); + } + } + } } From 4938f7920a216ee02eb172d91eb3c5b214301a54 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Mon, 28 Oct 2024 10:49:02 -0700 Subject: [PATCH 4/4] Rename options method on WasiPreview1.Builder --- .../test/java/com/dylibso/chicory/testing/MachinesTest.java | 3 +-- wabt/src/main/java/com/dylibso/chicory/wabt/Wast2Json.java | 2 +- wabt/src/main/java/com/dylibso/chicory/wabt/Wat2Wasm.java | 2 +- wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java | 2 +- .../test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/machine-tests/src/test/java/com/dylibso/chicory/testing/MachinesTest.java b/machine-tests/src/test/java/com/dylibso/chicory/testing/MachinesTest.java index e5109a822..84225961b 100644 --- a/machine-tests/src/test/java/com/dylibso/chicory/testing/MachinesTest.java +++ b/machine-tests/src/test/java/com/dylibso/chicory/testing/MachinesTest.java @@ -182,8 +182,7 @@ public void shouldUseMachineCallOnlyForExport() throws Exception { .withDirectory(target.toString(), target) .withArguments(List.of("wat2wasm", path.toString(), "--output=-")) .build(); - var logger = new SystemLogger(); - try (var wasi = WasiPreview1.builder().withLogger(logger).withOpts(wasiOpts).build()) { + try (var wasi = WasiPreview1.builder().withOptions(wasiOpts).build()) { ImportValues imports = new ImportValues(wasi.toHostFunctions()); var wat2WasmModule = Parser.parse(new File("../wabt/src/main/resources/wat2wasm")); var startFunctionIndex = new AtomicInteger(); diff --git a/wabt/src/main/java/com/dylibso/chicory/wabt/Wast2Json.java b/wabt/src/main/java/com/dylibso/chicory/wabt/Wast2Json.java index f9ab53840..ad07662df 100644 --- a/wabt/src/main/java/com/dylibso/chicory/wabt/Wast2Json.java +++ b/wabt/src/main/java/com/dylibso/chicory/wabt/Wast2Json.java @@ -84,7 +84,7 @@ public void process() { try (var wasi = WasiPreview1.builder() .withLogger(logger) - .withOpts(wasiOpts.build()) + .withOptions(wasiOpts.build()) .build()) { ImportValues imports = new ImportValues(wasi.toHostFunctions()); diff --git a/wabt/src/main/java/com/dylibso/chicory/wabt/Wat2Wasm.java b/wabt/src/main/java/com/dylibso/chicory/wabt/Wat2Wasm.java index 0f5a467d5..14187412c 100644 --- a/wabt/src/main/java/com/dylibso/chicory/wabt/Wat2Wasm.java +++ b/wabt/src/main/java/com/dylibso/chicory/wabt/Wat2Wasm.java @@ -70,7 +70,7 @@ private static byte[] parse(InputStream is, String fileName) { .build(); try (var wasi = - WasiPreview1.builder().withLogger(logger).withOpts(wasiOpts).build()) { + WasiPreview1.builder().withLogger(logger).withOptions(wasiOpts).build()) { ImportValues imports = new ImportValues(wasi.toHostFunctions()); Instance.builder(MODULE) .withMachineFactory(Wat2WasmModuleMachineFactory::create) diff --git a/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java b/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java index 6421c4df9..c027b33e0 100644 --- a/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java +++ b/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java @@ -116,7 +116,7 @@ public Builder withLogger(Logger logger) { return this; } - public Builder withOpts(WasiOptions opts) { + public Builder withOptions(WasiOptions opts) { this.opts = opts; return this; } diff --git a/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java b/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java index 15829b7ef..7ea54ae86 100644 --- a/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java +++ b/wasi/src/test/java/com/dylibso/chicory/wasi/WasiPreview1Test.java @@ -247,7 +247,7 @@ public void wasiPositionedWriteWithAppendShouldFail() throws IOException { var options = WasiOptions.builder().withDirectory(target.toString(), target).build(); - try (var wasi = WasiPreview1.builder().withOpts(options).build()) { + try (var wasi = WasiPreview1.builder().withOptions(options).build()) { var memory = new Memory(new MemoryLimits(1)); int fdPtr = 0;