Wasm: support file IO and random generator in WASI

This commit is contained in:
Alexey Andreev 2022-11-08 19:44:56 +01:00
parent e428e2ac7a
commit 224810d0ab
11 changed files with 141 additions and 62 deletions

View File

@ -18,6 +18,7 @@ package org.teavm.classlib.java.util;
import java.util.function.DoublePredicate;
import java.util.function.IntPredicate;
import java.util.function.LongPredicate;
import org.teavm.backend.wasm.runtime.WasmSupport;
import org.teavm.classlib.PlatformDetector;
import org.teavm.classlib.java.io.TSerializable;
import org.teavm.classlib.java.lang.TMath;
@ -144,6 +145,8 @@ public class TRandom extends TObject implements TSerializable {
public double nextDouble() {
if (PlatformDetector.isC()) {
return crand();
} else if (PlatformDetector.isWebAssembly()) {
return WasmSupport.random();
} else {
return random();
}

View File

@ -26,6 +26,7 @@ public final class WasmHeap {
public static final int PAGE_SIZE = 65536;
public static final int DEFAULT_STACK_SIZE = PAGE_SIZE * 4;
public static final int DEFAULT_REGION_SIZE = 1024;
public static final int DEFAULT_BUFFER_SIZE = 512;
public static int minHeapSize;
public static int maxHeapSize;
@ -42,6 +43,8 @@ public final class WasmHeap {
public static Address stackAddress;
public static Address stack;
public static int stackSize;
public static Address buffer;
public static int bufferSize;
private WasmHeap() {
}
@ -64,10 +67,13 @@ public final class WasmHeap {
WasmSupport.initHeapTrace(maxHeap);
}
public static void initHeap(Address start, int minHeap, int maxHeap, int stackSize) {
public static void initHeap(Address start, int minHeap, int maxHeap, int stackSize, int bufferSize) {
initHeapTrace(maxHeap);
stackAddress = start;
stack = start;
buffer = start;
buffer = WasmRuntime.align(start, 16);
WasmHeap.bufferSize = bufferSize;
stack = WasmRuntime.align(buffer.add(bufferSize), 16);
stackAddress = stack;
heapAddress = WasmRuntime.align(stackAddress.add(stackSize), 16);
memoryLimit = WasmRuntime.align(start, PAGE_SIZE);
minHeapSize = minHeap;

View File

@ -175,7 +175,7 @@ import org.teavm.vm.spi.TeaVMHostExtension;
public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
private static final MethodReference INIT_HEAP_REF = new MethodReference(WasmHeap.class, "initHeap",
Address.class, int.class, int.class, int.class, void.class);
Address.class, int.class, int.class, int.class, int.class, void.class);
private static final MethodReference RESIZE_HEAP_REF = new MethodReference(WasmHeap.class, "resizeHeap",
int.class, void.class);
private static final Set<MethodReference> VIRTUAL_METHODS = new HashSet<>(Arrays.asList(
@ -621,7 +621,8 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
initFunction.getBody().add(new WasmCall(names.forMethod(INIT_HEAP_REF),
new WasmInt32Constant(heapAddress), new WasmInt32Constant(minHeapSize),
new WasmInt32Constant(maxHeapSize), new WasmInt32Constant(WasmHeap.DEFAULT_STACK_SIZE)));
new WasmInt32Constant(maxHeapSize), new WasmInt32Constant(WasmHeap.DEFAULT_STACK_SIZE),
new WasmInt32Constant(WasmHeap.DEFAULT_BUFFER_SIZE)));
for (Class<?> javaCls : new Class<?>[] { GC.class }) {
ClassReader cls = classes.get(javaCls.getName());
@ -949,6 +950,8 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
int newRegionsCount = WasmHeap.calculateRegionsCount(maxHeapSize, WasmHeap.DEFAULT_REGION_SIZE);
int newRegionsSize = WasmHeap.calculateRegionsSize(newRegionsCount);
address = WasmRuntime.align(address, 16);
address = WasmRuntime.align(address + WasmHeap.DEFAULT_BUFFER_SIZE, 16);
address = WasmRuntime.align(address + WasmHeap.DEFAULT_STACK_SIZE, 16);
address = WasmRuntime.align(address + maxHeapSize, 16);
address = WasmRuntime.align(address + newRegionsSize, 16);

View File

@ -15,19 +15,18 @@
*/
package org.teavm.backend.wasm.runtime;
import org.teavm.backend.wasm.WasmHeap;
import org.teavm.interop.Address;
public final class WasiBuffer {
private static byte[] argBuffer = new byte[256];
private WasiBuffer() {
}
public static int getBufferSize() {
return argBuffer.length;
return WasmHeap.bufferSize;
}
public static Address getBuffer() {
return Address.ofData(argBuffer);
return WasmHeap.buffer;
}
}

View File

@ -15,8 +15,10 @@
*/
package org.teavm.backend.wasm.runtime;
import static org.teavm.backend.wasm.wasi.Wasi.ERRNO_SUCCESS;
import java.nio.charset.StandardCharsets;
import org.teavm.backend.wasm.wasi.IOVec;
import org.teavm.backend.wasm.wasi.IntResult;
import org.teavm.backend.wasm.wasi.LongResult;
import org.teavm.backend.wasm.wasi.SizeResult;
import org.teavm.backend.wasm.wasi.Wasi;
@ -26,6 +28,9 @@ import org.teavm.interop.Unmanaged;
@Unmanaged
public class WasiSupport {
private static long nextRandom;
private static boolean randomInitialized;
private WasiSupport() {
}
@ -43,7 +48,7 @@ public class WasiSupport {
public static void putChars(int fd, Address address, int count) {
Address argsAddress = WasiBuffer.getBuffer();
IOVec ioVec = argsAddress.toStructure();
SizeResult result = argsAddress.add(Structure.sizeOf(IOVec.class)).toStructure();
SizeResult result = Address.align(argsAddress.add(Structure.sizeOf(IOVec.class)), 16).toStructure();
ioVec.buffer = address;
ioVec.bufferLength = count;
Wasi.fdWrite(fd, ioVec, 1, result);
@ -51,8 +56,7 @@ public class WasiSupport {
@Unmanaged
public static long currentTimeMillis() {
Address argsAddress = WasiBuffer.getBuffer();
LongResult result = argsAddress.toStructure();
LongResult result = WasiBuffer.getBuffer().toStructure();
Wasi.clockTimeGet(Wasi.CLOCKID_REALTIME, 10, result);
return result.value;
}
@ -103,21 +107,22 @@ public class WasiSupport {
public static String[] getArgs() {
Address buffer = WasiBuffer.getBuffer();
SizeResult sizesReceiver = buffer.toStructure();
SizeResult bufferSizeReceiver = buffer.add(Structure.sizeOf(SizeResult.class)).toStructure();
IntResult sizesReceiver = buffer.toStructure();
IntResult bufferSizeReceiver = Address.align(buffer.add(Structure.sizeOf(IntResult.class)), 16)
.toStructure();
short errno = Wasi.argsSizesGet(sizesReceiver, bufferSizeReceiver);
if (errno != Wasi.ERRNO_SUCCESS) {
if (errno != ERRNO_SUCCESS) {
throw new RuntimeException("Could not get command line arguments");
}
int argvSize = sizesReceiver.value;
int argvBufSize = bufferSizeReceiver.value;
int argvSize = (int) sizesReceiver.value;
int argvBufSize = (int) bufferSizeReceiver.value;
int[] argvOffsets = new int[argvSize];
byte[] argvBuffer = new byte[argvBufSize];
errno = Wasi.argsGet(Address.ofData(argvOffsets), Address.ofData(argvBuffer));
if (errno != Wasi.ERRNO_SUCCESS) {
if (errno != ERRNO_SUCCESS) {
throw new RuntimeException("Could not get command line arguments");
}
@ -130,4 +135,23 @@ public class WasiSupport {
return args;
}
public static double random() {
return (((long) nextRandom(26) << 27) + nextRandom(27)) / (double) (1L << 53);
}
private static int nextRandom(int bits) {
if (!randomInitialized) {
short errno = Wasi.randomGet(WasiBuffer.getBuffer(), 8);
if (errno != ERRNO_SUCCESS) {
throw new RuntimeException("random_get: " + errno);
}
nextRandom = WasiBuffer.getBuffer().getLong();
}
nextRandom = ((nextRandom * 0x5DEECE66DL) + 0xBL) & ((1L << 48) - 1);
return (int) (nextRandom >>> (48 - bits));
}
}

View File

@ -53,4 +53,7 @@ public class WasmSupport {
public static String[] getArgs() {
return new String[0];
}
@Import(module = "teavmMath", name = "random")
public static native double random();
}

View File

@ -80,7 +80,7 @@ public class WasiVirtualFile implements VirtualFile {
if (baseFd < 0) {
return null;
}
Filestat filestat = Address.align(WasiBuffer.getBuffer(), 8).toStructure();
Filestat filestat = WasiBuffer.getBuffer().toStructure();
int errno;
if (path != null) {
byte[] bytes = path.getBytes(StandardCharsets.UTF_8);
@ -207,7 +207,7 @@ public class WasiVirtualFile implements VirtualFile {
fdflags |= FDFLAGS_APPEND;
}
int fd = open((short) 0, rights, fdflags);
return fd >= 0 ? new WasiVirtualFileAccessor(fs, fd) : null;
return fd >= 0 ? new WasiVirtualFileAccessor(this, fd) : null;
}
@Override

View File

@ -15,88 +15,88 @@
*/
package org.teavm.backend.wasm.runtime.fs;
import static org.teavm.backend.wasm.wasi.Wasi.ERRNO_SUCCESS;
import static org.teavm.backend.wasm.wasi.Wasi.WHENCE_CURRENT;
import static org.teavm.backend.wasm.wasi.Wasi.WHENCE_START;
import java.io.IOException;
import org.teavm.backend.wasm.runtime.WasiBuffer;
import org.teavm.backend.wasm.wasi.IOVec;
import org.teavm.backend.wasm.wasi.SizeResult;
import org.teavm.backend.wasm.wasi.Wasi;
import org.teavm.interop.Address;
import org.teavm.interop.Structure;
import org.teavm.runtime.fs.VirtualFileAccessor;
public class WasiVirtualFileAccessor implements VirtualFileAccessor {
// Enough room for an I32 plus padding for alignment:
private static final byte[] EIGHT_BYTE_BUFFER = new byte[8];
// Enough room for an I64 plus padding for alignment:
private static final byte[] SIXTEEN_BYTE_BUFFER = new byte[16];
private WasiFileSystem fs;
private WasiVirtualFile file;
private int fd;
public WasiVirtualFileAccessor(WasiFileSystem fs, int fd) {
this.fs = fs;
WasiVirtualFileAccessor(WasiVirtualFile file, int fd) {
this.file = file;
this.fd = fd;
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
byte[] vecBuffer = SIXTEEN_BYTE_BUFFER;
Address vec = WasiBuffer.getBuffer();
vec.putInt(Address.ofData(buffer).add(offset).toInt());
vec.add(4).putInt(length);
byte[] sizeBuffer = EIGHT_BYTE_BUFFER;
Address size = Address.align(Address.ofData(sizeBuffer), 4);
short errno = Wasi.fdRead(fd, vec, 1, size);
Address buf = WasiBuffer.getBuffer();
IOVec vec = buf.toStructure();
vec.buffer = Address.ofData(buffer).add(offset);
vec.bufferLength = length;
SizeResult sizeResult = Address.align(buf.add(Structure.sizeOf(IOVec.class)), 16).toStructure();
short errno = Wasi.fdRead(fd, vec, 1, sizeResult);
if (errno == ERRNO_SUCCESS) {
return size.getInt();
return (int) sizeResult.value;
} else {
throw new IOException(errnoMessage("fd_read", errno));
throw new IOException("fd_read: " + errno);
}
}
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
byte[] vecBuffer = SIXTEEN_BYTE_BUFFER;
Address vec = Address.align(Address.ofData(vecBuffer), 4);
byte[] sizeBuffer = EIGHT_BYTE_BUFFER;
Address size = Address.align(Address.ofData(sizeBuffer), 4);
Address buf = WasiBuffer.getBuffer();
IOVec vec = buf.toStructure();
SizeResult sizeResult = Address.align(buf.add(Structure.sizeOf(IOVec.class)), 16).toStructure();
int index = 0;
while (true) {
vec.putInt(Address.ofData(buffer).add(offset + index).toInt());
vec.add(4).putInt(length - index);
short errno = Wasi.fdWrite(fd, vec, 1, size);
vec.buffer = Address.ofData(buffer).add(offset);
vec.bufferLength = length;
short errno = Wasi.fdWrite(fd, vec, 1, sizeResult);
if (errno == ERRNO_SUCCESS) {
index += size.getInt();
if (index >= length) {
int size = (int) sizeResult.value;
offset += size;
length -= size;
if (length <= 0) {
return;
}
} else {
throw new IOException(errnoMessage("fd_write", errno));
throw new IOException("fd_write: " + errno);
}
}
}
@Override
public int tell() throws IOException {
byte[] filesizeBuffer = SIXTEEN_BYTE_BUFFER;
Address filesize = Address.align(Address.ofData(filesizeBuffer), 8);
SizeResult filesize = WasiBuffer.getBuffer().toStructure();
short errno = Wasi.fdTell(fd, filesize);
if (errno == ERRNO_SUCCESS) {
return (int) filesize.getLong();
return (int) filesize.value;
} else {
throw new IOException(errnoMessage("fd_tell", errno));
throw new IOException("fd_tell: " + errno);
}
}
private long seek(long offset, byte whence) throws IOException {
byte[] filesizeBuffer = SIXTEEN_BYTE_BUFFER;
Address filesize = Address.align(Address.ofData(filesizeBuffer), 8);
SizeResult filesize = WasiBuffer.getBuffer().toStructure();
short errno = Wasi.fdSeek(fd, offset, whence, filesize);
if (errno == ERRNO_SUCCESS) {
return filesize.getLong();
return filesize.value;
} else {
throw new IOException(errnoMessage("fd_seek", errno));
throw new IOException("fd_seek: " + errno);
}
}
@ -112,7 +112,7 @@ public class WasiVirtualFileAccessor implements VirtualFileAccessor {
@Override
public int size() throws IOException {
return (int) new WasiVirtualFile(fd, null).stat().filesize;
return (int) file.stat().filesize;
}
@Override
@ -120,7 +120,7 @@ public class WasiVirtualFileAccessor implements VirtualFileAccessor {
short errno = Wasi.fdFilestatSetSize(fd, size);
if (errno != ERRNO_SUCCESS) {
throw new IOException(errnoMessage("fd_filestat_set_size", errno));
throw new IOException("fd_filestat_set_size" + errno);
}
}
@ -133,7 +133,7 @@ public class WasiVirtualFileAccessor implements VirtualFileAccessor {
short errno = Wasi.fdClose(fd);
if (errno != ERRNO_SUCCESS) {
throw new IOException(errnoMessage("fd_close", errno));
throw new IOException("fd_close: " + errno);
}
}
}
@ -143,7 +143,7 @@ public class WasiVirtualFileAccessor implements VirtualFileAccessor {
short errno = Wasi.fdSync(fd);
if (errno != ERRNO_SUCCESS) {
throw new IOException(errnoMessage("fd_sync", errno));
throw new IOException("fd_sync: " + errno);
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2022 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.wasm.wasi;
import org.teavm.interop.Structure;
public class IntResult extends Structure {
public int value;
}

View File

@ -18,5 +18,5 @@ package org.teavm.backend.wasm.wasi;
import org.teavm.interop.Structure;
public class SizeResult extends Structure {
public int value;
public long value;
}

View File

@ -62,14 +62,23 @@ public final class Wasi {
public static native short clockTimeGet(int clockId, long precision, LongResult result);
@Import(name = "args_sizes_get", module = "wasi_snapshot_preview1")
public static native short argsSizesGet(SizeResult argvSize, SizeResult argvBufSize);
public static native short argsSizesGet(IntResult argvSize, IntResult argvBufSize);
@Import(name = "args_get", module = "wasi_snapshot_preview1")
public static native short argsGet(Address argv, Address argvBuf);
@Import(name = "fd_read", module = "wasi_snapshot_preview1")
public static native short fdRead(int fd, IOVec vecArray, int vecArrayLength, SizeResult size);
@Import(name = "fd_write", module = "wasi_snapshot_preview1")
public static native short fdWrite(int fd, IOVec vectors, int vectorsCont, SizeResult result);
@Import(name = "fd_tell", module = "wasi_snapshot_preview1")
public static native short fdTell(int fd, SizeResult size);
@Import(name = "fd_seek", module = "wasi_snapshot_preview1")
public static native short fdSeek(int fd, long offset, byte whence, SizeResult size);
@Import(name = "fd_prestat_get", module = "wasi_snapshot_preview1")
public static native short fdPrestatGet(int fd, Prestat prestat);
@ -109,4 +118,14 @@ public final class Wasi {
@Import(name = "path_filestat_set_times", module = "wasi_snapshot_preview1")
public static native short pathFilestatSetTimes(int fd, int lookupFlags, Address path, int pathLength,
long atime, long mtime, short fstflags);
@Import(name = "fd_filestat_set_size", module = "wasi_snapshot_preview1")
public static native short fdFilestatSetSize(int fd, long size);
@Import(name = "fd_sync", module = "wasi_snapshot_preview1")
public static native short fdSync(int fd);
@Import(name = "random_get", module = "wasi_snapshot_preview1")
public static native short randomGet(Address buffer, int bufferLength);
}