From 1ca2c75e1c0c3507f65e859fee52ed9a41ecbc6a Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 6 Nov 2022 11:53:52 +0100 Subject: [PATCH] Wasm: initial WASI support --- .../teavm/classlib/impl/console/Console.java | 39 ++++---- .../impl/console/ConsoleOutputStream.java | 32 +++++++ .../impl/console/StderrOutputStream.java | 7 +- .../impl/console/StdoutOutputStream.java | 7 +- .../org/teavm/classlib/java/lang/TSystem.java | 8 +- .../org/teavm/backend/wasm/TeaVMWasmHost.java | 2 + .../java/org/teavm/backend/wasm/WasmHeap.java | 7 +- .../org/teavm/backend/wasm/WasmRuntime.java | 22 +++-- .../teavm/backend/wasm/WasmRuntimeType.java | 21 ++++ .../org/teavm/backend/wasm/WasmTarget.java | 26 +++++ .../backend/wasm/runtime/WasiBuffer.java | 33 +++++++ .../backend/wasm/runtime/WasiSupport.java | 95 +++++++++++++++++++ .../backend/wasm/runtime/WasmSupport.java | 52 ++++++++++ .../WasiSupportClassTransformer.java | 62 ++++++++++++ .../org/teavm/backend/wasm/wasi/IOVec.java | 24 +++++ .../teavm/backend/wasm/wasi/LongResult.java | 22 +++++ .../teavm/backend/wasm/wasi/SizeResult.java | 22 +++++ .../org/teavm/backend/wasm/wasi/Wasi.java | 31 ++++++ .../org/teavm/backend/wasm/wasm-runtime.js | 11 ++- .../main/java/org/teavm/cli/TeaVMRunner.java | 7 +- .../org/teavm/tooling/TeaVMTargetType.java | 1 + .../java/org/teavm/tooling/TeaVMTool.java | 17 +++- 22 files changed, 503 insertions(+), 45 deletions(-) create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/console/ConsoleOutputStream.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/WasmRuntimeType.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/runtime/WasiBuffer.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/runtime/WasiSupport.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/runtime/WasmSupport.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/transformation/WasiSupportClassTransformer.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/wasi/IOVec.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/wasi/LongResult.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/wasi/SizeResult.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/wasi/Wasi.java diff --git a/classlib/src/main/java/org/teavm/classlib/impl/console/Console.java b/classlib/src/main/java/org/teavm/classlib/impl/console/Console.java index 217a017ea..7e493f2ae 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/console/Console.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/console/Console.java @@ -16,7 +16,9 @@ package org.teavm.classlib.impl.console; import org.teavm.backend.c.intrinsic.RuntimeInclude; +import org.teavm.backend.wasm.runtime.WasmSupport; import org.teavm.classlib.PlatformDetector; +import org.teavm.interop.Address; import org.teavm.interop.Import; import org.teavm.interop.Unmanaged; import org.teavm.jso.JSBody; @@ -25,29 +27,35 @@ public final class Console { private Console() { } - public static void writeStderr(int b) { + public static void writeStderr(byte[] data, int off, int len) { if (PlatformDetector.isC()) { - writeC(b); + for (int i = 0; i < len; ++i) { + byte b = data[i + off]; + writeC(b & 0xFF); + } } else if (PlatformDetector.isWebAssembly()) { - writeWasm(b); + WasmSupport.putCharsStderr(Address.ofData(data).add(off), len); } else { - writeJs(b); + for (int i = 0; i < len; ++i) { + byte b = data[i + off]; + writeJs(b & 0xFF); + } } } - public static void writeStdout(int b) { + public static void writeStdout(byte[] data, int off, int len) { if (PlatformDetector.isC()) { - writeC(b); + for (int i = 0; i < len; ++i) { + byte b = data[i + off]; + writeC(b & 0xFF); + } } else if (PlatformDetector.isWebAssembly()) { - writeWasm(b); + WasmSupport.putCharsStdout(Address.ofData(data).add(off), len); } else { - writeJsStdout(b); - } - } - - public static void writeStdout(String s) { - for (int i = 0; i < s.length(); ++i) { - writeStderr(s.charAt(i)); + for (int i = 0; i < len; ++i) { + byte b = data[i + off]; + writeJs(b & 0xFF); + } } } @@ -57,9 +65,6 @@ public final class Console { @JSBody(params = "b", script = "$rt_putStdout(b);") private static native void writeJsStdout(int b); - @Import(name = "putwchar", module = "teavm") - private static native void writeWasm(int b); - @Unmanaged @Import(name = "teavm_logchar") @RuntimeInclude("log.h") diff --git a/classlib/src/main/java/org/teavm/classlib/impl/console/ConsoleOutputStream.java b/classlib/src/main/java/org/teavm/classlib/impl/console/ConsoleOutputStream.java new file mode 100644 index 000000000..82e40a32f --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/console/ConsoleOutputStream.java @@ -0,0 +1,32 @@ +/* + * 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.classlib.impl.console; + +import java.io.IOException; +import java.io.OutputStream; + +public abstract class ConsoleOutputStream extends OutputStream { + private byte[] buffer = new byte[1]; + + @Override + public void write(int b) throws IOException { + buffer[0] = (byte) b; + write(buffer); + } + + @Override + public abstract void write(byte[] b, int off, int len) throws IOException; +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/console/StderrOutputStream.java b/classlib/src/main/java/org/teavm/classlib/impl/console/StderrOutputStream.java index 135d4f12a..7be710deb 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/console/StderrOutputStream.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/console/StderrOutputStream.java @@ -16,16 +16,15 @@ package org.teavm.classlib.impl.console; import java.io.IOException; -import java.io.OutputStream; -public class StderrOutputStream extends OutputStream { +public class StderrOutputStream extends ConsoleOutputStream { public static final StderrOutputStream INSTANCE = new StderrOutputStream(); private StderrOutputStream() { } @Override - public void write(int b) throws IOException { - Console.writeStderr(b); + public void write(byte[] b, int off, int len) throws IOException { + Console.writeStderr(b, off, len); } } diff --git a/classlib/src/main/java/org/teavm/classlib/impl/console/StdoutOutputStream.java b/classlib/src/main/java/org/teavm/classlib/impl/console/StdoutOutputStream.java index a831e3449..254036a7e 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/console/StdoutOutputStream.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/console/StdoutOutputStream.java @@ -16,16 +16,15 @@ package org.teavm.classlib.impl.console; import java.io.IOException; -import java.io.OutputStream; -public class StdoutOutputStream extends OutputStream { +public class StdoutOutputStream extends ConsoleOutputStream { public static final StdoutOutputStream INSTANCE = new StdoutOutputStream(); private StdoutOutputStream() { } @Override - public void write(int b) throws IOException { - Console.writeStdout(b); + public void write(byte[] b, int off, int len) throws IOException { + Console.writeStdout(b, off, len); } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java index 52c3ea37b..6c3f5d9a2 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java @@ -19,6 +19,7 @@ import java.util.Enumeration; import java.util.Properties; import org.teavm.backend.c.intrinsic.RuntimeInclude; import org.teavm.backend.javascript.spi.GeneratedBy; +import org.teavm.backend.wasm.runtime.WasmSupport; import org.teavm.classlib.PlatformDetector; import org.teavm.classlib.fs.VirtualFileSystemProvider; import org.teavm.classlib.fs.c.CFileSystem; @@ -144,15 +145,12 @@ public final class TSystem extends TObject { private static long currentTimeMillisLowLevel() { if (PlatformDetector.isWebAssembly()) { - return (long) currentTimeMillisWasm(); + return WasmSupport.currentTimeMillis(); } else { - return (long) currentTimeMillisC(); + return currentTimeMillisC(); } } - @Import(name = "currentTimeMillis", module = "teavm") - private static native double currentTimeMillisWasm(); - @Import(name = "teavm_currentTimeMillis") @RuntimeInclude("time.h") private static native long currentTimeMillisC(); diff --git a/core/src/main/java/org/teavm/backend/wasm/TeaVMWasmHost.java b/core/src/main/java/org/teavm/backend/wasm/TeaVMWasmHost.java index 9c881d803..4c23a0de0 100644 --- a/core/src/main/java/org/teavm/backend/wasm/TeaVMWasmHost.java +++ b/core/src/main/java/org/teavm/backend/wasm/TeaVMWasmHost.java @@ -20,4 +20,6 @@ import org.teavm.vm.spi.TeaVMHostExtension; public interface TeaVMWasmHost extends TeaVMHostExtension { void add(WasmIntrinsicFactory intrinsicFactory); + + WasmRuntimeType getRuntimeType(); } 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 e1fa841bf..82d63fe0d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmHeap.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmHeap.java @@ -15,8 +15,8 @@ */ package org.teavm.backend.wasm; +import org.teavm.backend.wasm.runtime.WasmSupport; import org.teavm.interop.Address; -import org.teavm.interop.Import; import org.teavm.interop.StaticInit; import org.teavm.interop.Unmanaged; @@ -60,8 +60,9 @@ public final class WasmHeap { public static native void growMemory(int amount); - @Import(name = "init", module = "teavmHeapTrace") - private static native void initHeapTrace(int maxHeap); + private static void initHeapTrace(int maxHeap) { + WasmSupport.initHeapTrace(maxHeap); + } public static void initHeap(Address start, int minHeap, int maxHeap, int stackSize) { initHeapTrace(maxHeap); diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java b/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java index 6602908e7..f7d61f49d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java @@ -15,8 +15,8 @@ */ package org.teavm.backend.wasm; +import org.teavm.backend.wasm.runtime.WasmSupport; import org.teavm.interop.Address; -import org.teavm.interop.Import; import org.teavm.interop.StaticInit; import org.teavm.interop.Unmanaged; import org.teavm.runtime.RuntimeObject; @@ -84,17 +84,21 @@ public final class WasmRuntime { return value; } - @Import(name = "print", module = "spectest") - public static native void print(int a); + public static void print(int a) { + WasmSupport.print(a); + } - @Import(name = "logString", module = "teavm") - public static native void printString(String s); + public static void printString(String s) { + WasmSupport.printString(s); + } - @Import(name = "logInt", module = "teavm") - public static native void printInt(int i); + public static void printInt(int i) { + WasmSupport.printInt(i); + } - @Import(name = "logOutOfMemory", module = "teavm") - public static native void printOutOfMemory(); + public static void printOutOfMemory() { + WasmSupport.printOutOfMemory(); + } public static void fillZero(Address address, int count) { fill(address, (byte) 0, count); diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmRuntimeType.java b/core/src/main/java/org/teavm/backend/wasm/WasmRuntimeType.java new file mode 100644 index 000000000..7545556d2 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/WasmRuntimeType.java @@ -0,0 +1,21 @@ +/* + * 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; + +public enum WasmRuntimeType { + TEAVM, + WASI +} 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 d15343252..62d4f6b45 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -106,6 +106,7 @@ import org.teavm.backend.wasm.render.WasmCRenderer; import org.teavm.backend.wasm.render.WasmRenderer; import org.teavm.backend.wasm.transformation.IndirectCallTraceTransformation; import org.teavm.backend.wasm.transformation.MemoryAccessTraceTransformation; +import org.teavm.backend.wasm.transformation.WasiSupportClassTransformer; import org.teavm.common.ServiceRepository; import org.teavm.dependency.ClassDependency; import org.teavm.dependency.DependencyAnalyzer; @@ -199,6 +200,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { private boolean obfuscated; private Set asyncMethods; private boolean hasThreads; + private WasmRuntimeType runtimeType = WasmRuntimeType.TEAVM; @Override public void setController(TeaVMTargetController controller) { @@ -233,6 +235,9 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { List transformers = new ArrayList<>(); transformers.add(new ClassPatch()); transformers.add(new WasmDependencyListener()); + if (runtimeType == WasmRuntimeType.WASI) { + transformers.add(new WasiSupportClassTransformer()); + } return transformers; } @@ -291,6 +296,15 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { this.obfuscated = obfuscated; } + public void setRuntimeType(WasmRuntimeType runtimeType) { + this.runtimeType = runtimeType; + } + + @Override + public WasmRuntimeType getRuntimeType() { + return runtimeType; + } + @Override public void contributeDependencies(DependencyAnalyzer dependencyAnalyzer) { for (Class type : Arrays.asList(int.class, long.class, float.class, double.class)) { @@ -502,6 +516,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { module.add(initFunction); module.setStartFunction(initFunction); module.add(createStartFunction(names)); + module.add(createStartCallerFunction()); for (String functionName : classGenerator.getFunctionTable()) { WasmFunction function = module.getFunctions().get(functionName); @@ -553,6 +568,17 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { return function; } + private WasmFunction createStartCallerFunction() { + WasmFunction function = new WasmFunction("teavm_call_start"); + function.setExportName("_start"); + + WasmCall call = new WasmCall("teavm_start"); + call.getArguments().add(new WasmInt32Constant(0)); + function.getBody().add(call); + + return function; + } + private class IntrinsicFactoryContext implements WasmIntrinsicFactoryContext { @Override public ClassReaderSource getClassSource() { 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 new file mode 100644 index 000000000..3591cb429 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/WasiBuffer.java @@ -0,0 +1,33 @@ +/* + * 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.runtime; + +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; + } + + public static Address getBuffer() { + return Address.ofData(argBuffer); + } +} 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 new file mode 100644 index 000000000..a4b4bc6c1 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/WasiSupport.java @@ -0,0 +1,95 @@ +/* + * 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.runtime; + +import org.teavm.backend.wasm.wasi.IOVec; +import org.teavm.backend.wasm.wasi.LongResult; +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.interop.Unmanaged; + +@Unmanaged +public class WasiSupport { + private WasiSupport() { + } + + @Unmanaged + public static void putCharsStdout(Address address, int count) { + putChars(1, address, count); + } + + @Unmanaged + public static void putCharsStderr(Address address, int count) { + putChars(2, address, count); + } + + @Unmanaged + 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(); + ioVec.buffer = address; + ioVec.bufferLength = count; + Wasi.fdWrite(fd, ioVec, 1, result); + } + + @Unmanaged + public static long currentTimeMillis() { + Address argsAddress = WasiBuffer.getBuffer(); + LongResult result = argsAddress.toStructure(); + Wasi.clockTimeGet(Wasi.CLOCKID_REALTIME, 10, result); + return result.value; + } + + @Unmanaged + public static void printString(String s) { + int charsInChunk = 128; + int offsetInBuffer = 128; + Address buffer = WasiBuffer.getBuffer().add(offsetInBuffer); + for (int i = 0; i < s.length(); i += charsInChunk) { + int end = Math.min(s.length(), i + charsInChunk); + int count = end - i; + for (int j = 0; j < count; ++j) { + buffer.add(offsetInBuffer + j).putByte((byte) s.charAt(j)); + } + putCharsStderr(buffer, count); + } + } + + @Unmanaged + public static void printInt(int i) { + int count = 0; + Address buffer = WasiBuffer.getBuffer().add(WasiBuffer.getBufferSize()); + do { + ++count; + buffer = buffer.add(-1); + buffer.putByte((byte) ((i % 10) + (int) '0')); + i /= 10; + } while (i > 0); + putCharsStderr(buffer, count); + } + + @Unmanaged + public static void printOutOfMemory() { + printString("Out of memory"); + } + + @Unmanaged + public static void initHeapTrace(int maxHeap) { + } +} 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 new file mode 100644 index 000000000..2cded5b7f --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/WasmSupport.java @@ -0,0 +1,52 @@ +/* + * 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.runtime; + +import org.teavm.interop.Address; +import org.teavm.interop.Import; + +public class WasmSupport { + private WasmSupport() { + } + + @Import(name = "putwcharsOut", module = "teavm") + public static native void putCharsStderr(Address address, int count); + + @Import(name = "putwcharsErr", module = "teavm") + public static native void putCharsStdout(Address address, int count); + + public static long currentTimeMillis() { + return (long) currentTimeMillisImpl(); + } + + @Import(name = "currentTimeMillis", module = "teavm") + private static native double currentTimeMillisImpl(); + + @Import(name = "print", module = "spectest") + public static native void print(int a); + + @Import(name = "logString", module = "teavm") + public static native void printString(String s); + + @Import(name = "logInt", module = "teavm") + public static native void printInt(int i); + + @Import(name = "logOutOfMemory", module = "teavm") + public static native void printOutOfMemory(); + + @Import(name = "init", module = "teavmHeapTrace") + public static native void initHeapTrace(int maxHeap); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/transformation/WasiSupportClassTransformer.java b/core/src/main/java/org/teavm/backend/wasm/transformation/WasiSupportClassTransformer.java new file mode 100644 index 000000000..578716b23 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/transformation/WasiSupportClassTransformer.java @@ -0,0 +1,62 @@ +/* + * 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.transformation; + +import org.teavm.backend.wasm.runtime.WasiSupport; +import org.teavm.backend.wasm.runtime.WasmSupport; +import org.teavm.model.ClassHierarchy; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; +import org.teavm.model.ClassReader; +import org.teavm.model.ElementModifier; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReader; +import org.teavm.model.ValueType; +import org.teavm.model.emit.ProgramEmitter; +import org.teavm.model.emit.ValueEmitter; + +public class WasiSupportClassTransformer implements ClassHolderTransformer { + @Override + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { + if (cls.getName().equals(WasmSupport.class.getName())) { + transformWasm(cls, context.getHierarchy()); + } + } + + private void transformWasm(ClassHolder cls, ClassHierarchy classHierarchy) { + ClassReader sourceCls = classHierarchy.getClassSource().get(WasiSupport.class.getName()); + for (MethodHolder method : cls.getMethods()) { + MethodReader sourceMethod = sourceCls.getMethod(method.getDescriptor()); + if (sourceMethod != null) { + if (method.hasModifier(ElementModifier.NATIVE)) { + method.getModifiers().remove(ElementModifier.NATIVE); + } + ProgramEmitter pe = ProgramEmitter.create(method, classHierarchy); + ValueEmitter[] args = new ValueEmitter[method.parameterCount()]; + for (int i = 0; i < args.length; ++i) { + args[i] = pe.var(i + 1, method.parameterType(i)); + } + ValueEmitter result = pe.invoke(sourceMethod.getReference(), args); + if (method.getResultType() != ValueType.VOID) { + result.returnValue(); + } else { + pe.exit(); + } + } + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/wasi/IOVec.java b/core/src/main/java/org/teavm/backend/wasm/wasi/IOVec.java new file mode 100644 index 000000000..653bc8235 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/wasi/IOVec.java @@ -0,0 +1,24 @@ +/* + * 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.Address; +import org.teavm.interop.Structure; + +public class IOVec extends Structure { + public Address buffer; + public int bufferLength; +} diff --git a/core/src/main/java/org/teavm/backend/wasm/wasi/LongResult.java b/core/src/main/java/org/teavm/backend/wasm/wasi/LongResult.java new file mode 100644 index 000000000..3f964efc9 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/wasi/LongResult.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 LongResult extends Structure { + public long 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 new file mode 100644 index 000000000..31dae0a98 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/wasi/SizeResult.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 SizeResult extends Structure { + public int 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 new file mode 100644 index 000000000..5515d34cf --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/wasi/Wasi.java @@ -0,0 +1,31 @@ +/* + * 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.Import; + +public final class Wasi { + public static final int CLOCKID_REALTIME = 0; + + private Wasi() { + } + + @Import(name = "fd_write", module = "wasi_snapshot_preview1") + public static native short fdWrite(int fd, IOVec vectors, int vectorsCont, SizeResult result); + + @Import(name = "clock_time_get", module = "wasi_snapshot_preview1") + public static native short clockTimeGet(int clockId, long precision, LongResult result); +} diff --git a/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js b/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js index 07385042e..d1ca9edad 100644 --- a/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js +++ b/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js @@ -31,6 +31,14 @@ TeaVM.wasm = function() { lineBuffer += String.fromCharCode(charCode); } } + function putwchars(buffer, count) { + let instance = controller.instance; + let memory = instance.exports.memory.buffer; + for (let i = 0; i < count; ++i) { + // TODO: support UTF-8 + putwchar(memory[buffer++]); + } + } function towlower(code) { return String.fromCharCode(code).toLowerCase().charCodeAt(0); } @@ -90,7 +98,8 @@ TeaVM.wasm = function() { teavm_getNaN: function() { return NaN; }, isinf: function(n) { return !isFinite(n) }, isfinite: isFinite, - putwchar: putwchar, + putwcharsOut: (chars, count) => putwchars(controller, chars, count), + putwcharsErr: (chars, count) => putwchars(controller, chars, count), towlower: towlower, towupper: towupper, getNativeOffset: getNativeOffset, diff --git a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java index a2017edb9..afa963d17 100644 --- a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java +++ b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java @@ -58,7 +58,7 @@ public final class TeaVMRunner { options.addOption(Option.builder("t") .argName("target") .hasArg() - .desc("target type (javascript/js, webassembly/wasm, C)") + .desc("target type (javascript/js, webassembly/wasm, webassembly-wasi/wasm-wasi/wasi, C)") .build()); options.addOption(Option.builder("d") .argName("directory") @@ -220,6 +220,11 @@ public final class TeaVMRunner { case "wasm": tool.setTargetType(TeaVMTargetType.WEBASSEMBLY); break; + case "webassembly-wasi": + case "wasm-wasi": + case "wasi": + tool.setTargetType(TeaVMTargetType.WEBASSEMBLY); + break; case "c": tool.setTargetType(TeaVMTargetType.C); break; diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTargetType.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTargetType.java index 77d816ca7..b7d6ac36b 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTargetType.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTargetType.java @@ -18,5 +18,6 @@ package org.teavm.tooling; public enum TeaVMTargetType { JAVASCRIPT, WEBASSEMBLY, + WEBASSEMBLY_WASI, C } diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index 785f750ba..ba044cc80 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -37,6 +37,7 @@ import org.teavm.backend.c.generate.CNameProvider; import org.teavm.backend.c.generate.ShorteningFileNameProvider; import org.teavm.backend.c.generate.SimpleFileNameProvider; import org.teavm.backend.javascript.JavaScriptTarget; +import org.teavm.backend.wasm.WasmRuntimeType; import org.teavm.backend.wasm.WasmTarget; import org.teavm.backend.wasm.render.WasmBinaryVersion; import org.teavm.cache.AlwaysStaleCacheStatus; @@ -315,7 +316,9 @@ public class TeaVMTool { case JAVASCRIPT: return prepareJavaScriptTarget(); case WEBASSEMBLY: - return prepareWebAssemblyTarget(); + return prepareWebAssemblyDefaultTarget(); + case WEBASSEMBLY_WASI: + return prepareWebAssemblyWasiTarget(); case C: return prepareCTarget(); } @@ -347,6 +350,18 @@ public class TeaVMTool { return webAssemblyTarget; } + private WasmTarget prepareWebAssemblyDefaultTarget() { + WasmTarget target = prepareWebAssemblyTarget(); + target.setRuntimeType(WasmRuntimeType.TEAVM); + return target; + } + + private WasmTarget prepareWebAssemblyWasiTarget() { + WasmTarget target = prepareWebAssemblyTarget(); + target.setRuntimeType(WasmRuntimeType.WASI); + return target; + } + private CTarget prepareCTarget() { cTarget = new CTarget(new CNameProvider()); cTarget.setMinHeapSize(minHeapSize);