diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java b/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java index aeaca755f..30c2401ff 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java @@ -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(); } diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmHeap.java b/core/src/main/java/org/teavm/backend/wasm/WasmHeap.java index 82d63fe0d..696c05d13 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmHeap.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmHeap.java @@ -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; diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index 31e498624..fe6fbb76a 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -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 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); diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/WasiBuffer.java b/core/src/main/java/org/teavm/backend/wasm/runtime/WasiBuffer.java index 3591cb429..1c7acb42f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/runtime/WasiBuffer.java +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/WasiBuffer.java @@ -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; } } diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/WasiSupport.java b/core/src/main/java/org/teavm/backend/wasm/runtime/WasiSupport.java index 290380794..17cfa61cb 100644 --- a/core/src/main/java/org/teavm/backend/wasm/runtime/WasiSupport.java +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/WasiSupport.java @@ -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)); + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/WasmSupport.java b/core/src/main/java/org/teavm/backend/wasm/runtime/WasmSupport.java index 0fbaaebd3..af77763d9 100644 --- a/core/src/main/java/org/teavm/backend/wasm/runtime/WasmSupport.java +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/WasmSupport.java @@ -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(); } diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFile.java b/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFile.java index c5d370b28..2362d6d6d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFile.java +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFile.java @@ -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 diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFileAccessor.java b/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFileAccessor.java index 1df7433ef..9616ca982 100644 --- a/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFileAccessor.java +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFileAccessor.java @@ -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); } } } \ No newline at end of file diff --git a/core/src/main/java/org/teavm/backend/wasm/wasi/IntResult.java b/core/src/main/java/org/teavm/backend/wasm/wasi/IntResult.java new file mode 100644 index 000000000..aacf0ba55 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/wasi/IntResult.java @@ -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; +} diff --git a/core/src/main/java/org/teavm/backend/wasm/wasi/SizeResult.java b/core/src/main/java/org/teavm/backend/wasm/wasi/SizeResult.java index 31dae0a98..7495075ed 100644 --- a/core/src/main/java/org/teavm/backend/wasm/wasi/SizeResult.java +++ b/core/src/main/java/org/teavm/backend/wasm/wasi/SizeResult.java @@ -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; } diff --git a/core/src/main/java/org/teavm/backend/wasm/wasi/Wasi.java b/core/src/main/java/org/teavm/backend/wasm/wasi/Wasi.java index b181eb6e7..e413e90d9 100644 --- a/core/src/main/java/org/teavm/backend/wasm/wasi/Wasi.java +++ b/core/src/main/java/org/teavm/backend/wasm/wasi/Wasi.java @@ -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); + }