mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Wasm: support file IO and random generator in WASI
This commit is contained in:
parent
e428e2ac7a
commit
224810d0ab
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user