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 35adf7aef..178f30441 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -27,9 +27,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.Set; +import org.teavm.ast.InvocationExpr; import org.teavm.ast.decompilation.Decompiler; import org.teavm.backend.lowlevel.analyze.LowLevelInliningFilterFactory; import org.teavm.backend.lowlevel.dependency.StringsDependencyListener; @@ -69,8 +71,10 @@ import org.teavm.backend.wasm.intrinsics.RuntimeClassIntrinsic; import org.teavm.backend.wasm.intrinsics.ShadowStackIntrinsic; import org.teavm.backend.wasm.intrinsics.StructureIntrinsic; import org.teavm.backend.wasm.intrinsics.WasmHeapIntrinsic; +import org.teavm.backend.wasm.intrinsics.WasmIntrinsic; import org.teavm.backend.wasm.intrinsics.WasmIntrinsicFactory; import org.teavm.backend.wasm.intrinsics.WasmIntrinsicFactoryContext; +import org.teavm.backend.wasm.intrinsics.WasmIntrinsicManager; import org.teavm.backend.wasm.intrinsics.WasmRuntimeIntrinsic; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmLocal; @@ -92,6 +96,7 @@ import org.teavm.backend.wasm.model.expression.WasmLoadInt32; import org.teavm.backend.wasm.model.expression.WasmReturn; import org.teavm.backend.wasm.model.expression.WasmSetLocal; import org.teavm.backend.wasm.model.expression.WasmStoreInt32; +import org.teavm.backend.wasm.model.expression.WasmUnreachable; import org.teavm.backend.wasm.optimization.UnusedFunctionElimination; import org.teavm.backend.wasm.render.WasmBinaryRenderer; import org.teavm.backend.wasm.render.WasmBinaryVersion; @@ -150,7 +155,9 @@ import org.teavm.model.transformation.BoundCheckInsertion; import org.teavm.model.transformation.ClassPatch; import org.teavm.model.transformation.NullCheckInsertion; import org.teavm.runtime.Allocator; +import org.teavm.runtime.EventQueue; import org.teavm.runtime.ExceptionHandling; +import org.teavm.runtime.Fiber; import org.teavm.runtime.RuntimeArray; import org.teavm.runtime.RuntimeClass; import org.teavm.runtime.RuntimeObject; @@ -354,6 +361,22 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { } } + dependencyAnalyzer.linkMethod(new MethodReference(Fiber.class, "isResuming", boolean.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(Fiber.class, "isSuspending", boolean.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(Fiber.class, "current", Fiber.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(Fiber.class, "startMain", String[].class, void.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(EventQueue.class, "processSingle", void.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(EventQueue.class, "isStopped", boolean.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(Thread.class, "setCurrentThread", Thread.class, + void.class)).use(); + + ClassReader fiberClass = dependencyAnalyzer.getClassSource().get(Fiber.class.getName()); + for (MethodReader method : fiberClass.getMethods()) { + if (method.getName().startsWith("pop") || method.getName().equals("push")) { + dependencyAnalyzer.linkMethod(method.getReference()).use(); + } + } + dependencyAnalyzer.addDependencyListener(new StringsDependencyListener()); } @@ -417,6 +440,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { context.addIntrinsic(new MemoryTraceIntrinsic()); } context.addIntrinsic(new WasmHeapIntrinsic()); + context.addIntrinsic(new FiberIntrinsic()); IntrinsicFactoryContext intrinsicFactoryContext = new IntrinsicFactoryContext(); for (WasmIntrinsicFactory additionalIntrinsicFactory : additionalIntrinsics) { @@ -459,15 +483,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { generateInitFunction(classes, initFunction, names, binaryWriter.getAddress()); module.add(initFunction); module.setStartFunction(initFunction); - - - for (TeaVMEntryPoint entryPoint : controller.getEntryPoints().values()) { - String mangledName = names.forMethod(entryPoint.getMethod()); - WasmFunction function = module.getFunctions().get(mangledName); - if (function != null) { - function.setExportName(entryPoint.getPublicName()); - } - } + module.add(createStartFunction(names)); for (String functionName : classGenerator.getFunctionTable()) { WasmFunction function = module.getFunctions().get(functionName); @@ -503,6 +519,22 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { emitRuntime(buildTarget, getBaseName(outputName) + ".wasm-runtime.js"); } + private WasmFunction createStartFunction(NameProvider names) { + WasmFunction function = new WasmFunction("teavm_start"); + function.setExportName("start"); + function.getParameters().add(WasmType.INT32); + + WasmLocal local = new WasmLocal(WasmType.INT32, "args"); + function.add(local); + + WasmCall call = new WasmCall(names.forMethod(new MethodReference(Fiber.class, "startMain", String[].class, + void.class))); + call.getArguments().add(new WasmGetLocal(local)); + function.getBody().add(call); + + return function; + } + private class IntrinsicFactoryContext implements WasmIntrinsicFactoryContext { @Override public ClassReaderSource getClassSource() { @@ -967,4 +999,53 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { return classSource; } } + + + class FiberIntrinsic implements WasmIntrinsic { + @Override + public boolean isApplicable(MethodReference methodReference) { + if (!methodReference.getClassName().equals(Fiber.class.getName())) { + return false; + } + switch (methodReference.getName()) { + case "runMain": + case "setCurrentThread": + return true; + default: + return false; + } + } + + @Override + public WasmExpression apply(InvocationExpr invocation, WasmIntrinsicManager manager) { + switch (invocation.getMethod().getName()) { + case "runMain": { + Iterator entryPointIter = controller.getEntryPoints().values() + .iterator(); + if (entryPointIter.hasNext()) { + TeaVMEntryPoint entryPoint = entryPointIter.next(); + String name = manager.getNames().forMethod(entryPoint.getMethod()); + WasmCall call = new WasmCall(name); + call.getArguments().add(manager.generate(invocation.getArguments().get(0))); + call.setLocation(invocation.getLocation()); + return call; + } else { + WasmUnreachable unreachable = new WasmUnreachable(); + unreachable.setLocation(invocation.getLocation()); + return unreachable; + } + } + case "setCurrentThread": { + String name = manager.getNames().forMethod(new MethodReference(Thread.class, + "setCurrentThread", Thread.class, void.class)); + WasmCall call = new WasmCall(name); + call.getArguments().add(manager.generate(invocation.getArguments().get(0))); + call.setLocation(invocation.getLocation()); + return call; + } + default: + throw new IllegalArgumentException(); + } + } + } } \ No newline at end of file diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java index 67414dd2a..d6ff49c10 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java @@ -701,7 +701,7 @@ class WasmCRenderingVisitor implements WasmExpressionVisitor { StringBuilder sb = new StringBuilder(); if (expression.isImported()) { - sb.append(!function.getImportModule().isEmpty() + sb.append(function.getImportModule() != null && !function.getImportModule().isEmpty() ? function.getImportModule() + "_" + function.getImportName() : function.getImportName()); } else { diff --git a/core/src/main/java/org/teavm/runtime/EventQueue.java b/core/src/main/java/org/teavm/runtime/EventQueue.java index 3e656e559..dcc1c228a 100644 --- a/core/src/main/java/org/teavm/runtime/EventQueue.java +++ b/core/src/main/java/org/teavm/runtime/EventQueue.java @@ -17,10 +17,9 @@ package org.teavm.runtime; import java.util.Arrays; import org.teavm.backend.c.intrinsic.RuntimeInclude; +import org.teavm.interop.Export; import org.teavm.interop.Import; -import org.teavm.interop.Platforms; import org.teavm.interop.StaticInit; -import org.teavm.interop.UnsupportedOn; @StaticInit public final class EventQueue { @@ -72,6 +71,31 @@ public final class EventQueue { } } + @Export(name = "teavm_processQueue") + public static long processSingle() { + if (size == 0) { + return -1; + } + + Node node = data[0]; + long currentTime = System.currentTimeMillis(); + if (node.time <= System.currentTimeMillis()) { + remove(0); + node.event.run(); + if (size == 0) { + return -1; + } + return Math.max(0, node.time - currentTime); + } else { + return node.time - currentTime; + } + } + + @Export(name = "teavm_stopped") + public static boolean isStopped() { + return finished; + } + public static void stop() { finished = true; } @@ -129,12 +153,10 @@ public final class EventQueue { @Import(name = "teavm_waitFor") @RuntimeInclude("fiber.h") - @UnsupportedOn(Platforms.WEBASSEMBLY) private static native void waitFor(long time); - @Import(name = "teavm_interrupt") + @Import(name = "teavm_interrupt", module = "teavm") @RuntimeInclude("fiber.h") - @UnsupportedOn(Platforms.WEBASSEMBLY) private static native void interrupt(); private static void ensureCapacity(int capacity) { 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 d5ee00b3d..07385042e 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 @@ -43,19 +43,46 @@ TeaVM.wasm = function() { function getNativeOffset(instant) { return new Date(instant).getTimezoneOffset(); } - function logString(string) { - var memory = new DataView(logString.memory.buffer); - var arrayPtr = memory.getUint32(string + 8, true); - var length = memory.getUint32(arrayPtr + 8, true); - for (var i = 0; i < length; ++i) { - putwchar(memory.getUint16(i * 2 + arrayPtr + 12, true)); + function logString(string, controller) { + let instance = controller.instance; + let memory = instance.exports.memory.buffer; + let arrayPtr = instance.exports.teavm_stringData(string); + let length = instance.exports.teavm_arrayLength(arrayPtr); + let arrayData = new DataView(memory, instance.exports.teavm_charArrayData(arrayPtr), length * 2); + for (let i = 0; i < length; ++i) { + putwchar(arrayData.memory.getUint16(i * 2, true)); } } function logInt(i) { lineBuffer += i.toString(); } + function interrupt(controller) { + if (controller.timer !== null) { + clearTimeout(controller.timer); + controller.timer = null; + } + controller.timer = setTimeout(() => process(controller), 0); + } + function process(controller) { + let result = controller.instance.exports.teavm_processQueue(); + if (!controller.complete) { + if (controller.instance.exports.teavm_stopped()) { + controller.complete = true; + controller.resolve(); + } + } + if (result >= 0) { + controller.timer = setTimeout(() => process(controller), result) + } + } - function importDefaults(obj) { + function defaults(obj) { + let controller = {}; + controller.instance = null; + controller.timer = null; + controller.resolve = null; + controller.reject = null; + controller.complete = false; obj.teavm = { currentTimeMillis: currentTimeMillis, nanoTime: function() { return performance.now(); }, @@ -67,9 +94,10 @@ TeaVM.wasm = function() { towlower: towlower, towupper: towupper, getNativeOffset: getNativeOffset, - logString: logString, + logString: string => logString(string, controller), logInt: logInt, - logOutOfMemory: function() { console.log("Out of memory") } + logOutOfMemory: () => console.log("Out of memory"), + teavm_interrupt: () => interrupt(controller) }; obj.teavmMath = Math; @@ -91,6 +119,8 @@ TeaVM.wasm = function() { gcCompleted: function() {}, init: function(maxHeap) {} }; + + return controller; } function createTeaVM(instance) { @@ -109,35 +139,29 @@ TeaVM.wasm = function() { } teavm.main = createMain(teavm, instance.exports.main); - return teavm; } function wrapExport(fn, instance) { return function() { let result = fn.apply(this, arguments); - let ex = instance.exports.teavm_catchException(); - if (ex !== 0) { - throw new JavaError("Uncaught exception occurred in java"); + let ex = catchException(instance); + if (ex !== null) { + throw ex; } return result; } } + function catchException(instance) { + let ex = instance.exports.teavm_catchException(); + if (ex !== 0) { + return new JavaError("Uncaught exception occurred in Java"); + } + return null; + } + function load(path, options) { - if (!options) { - options = {}; - } - - let callback = typeof options.callback !== "undefined" ? options.callback : function() {}; - let errorCallback = typeof options.errorCallback !== "undefined" ? options.errorCallback : function() {}; - - let importObj = {}; - importDefaults(importObj); - if (typeof options.installImports !== "undefined") { - options.installImports(importObj); - } - let xhr = new XMLHttpRequest(); xhr.responseType = "arraybuffer"; xhr.open("GET", path); @@ -146,45 +170,61 @@ TeaVM.wasm = function() { xhr.onload = () => { let response = xhr.response; if (!response) { + reject("Error loading Wasm data") return; } - WebAssembly.instantiate(response, importObj).then(resultObject => { - importObj.teavm.logString.memory = resultObject.instance.exports.memory; - let teavm = createTeaVM(resultObject.instance); - teavm.main = createMain(teavm, wrapExport, resultObject.instance.exports.main); - resolve(teavm); - }).catch(error => { - reject(error); - }); + resolve(response); }; xhr.send(); + }).then(data => create(data, options)); + } + + function create(data, options) { + if (!options) { + options = {}; + } + + const importObj = {}; + const controller = defaults(importObj); + if (typeof options.installImports !== "undefined") { + options.installImports(importObj); + } + + return WebAssembly.instantiate(data, importObj).then(resultObject => { + controller.instance = resultObject.instance; + let teavm = createTeaVM(resultObject.instance); + teavm.main = createMain(teavm, controller); + return teavm; }); } - function createMain(teavm, mainFunction) { + function createMain(teavm, controller) { return function(args) { if (typeof args === "undefined") { args = []; } - return new Promise(resolve => { - let javaArgs = teavm.allocateStringArray(mainArgs.length); - let javaArgsData = new Uint32Array(teavm.memory, teavm.objectArrayData(javaArgs), args.length); - for (let i = 0; i < mainArgs.length; ++i) { + return new Promise((resolve, reject) => { + let javaArgs = teavm.allocateStringArray(args.length); + let javaArgsData = new DataView(teavm.memory.buffer, teavm.objectArrayData(javaArgs), args.length * 4); + for (let i = 0; i < args.length; ++i) { let arg = args[i]; let javaArg = teavm.allocateString(arg.length); let javaArgAddress = teavm.objectArrayData(teavm.stringData(javaArg)); - let javaArgData = new Uint16Array(teavm.memory, javaArgAddress, arg.length); + let javaArgData = new DataView(teavm.memory.buffer, javaArgAddress, arg.length * 2); for (let j = 0; j < arg.length; ++j) { - javaArgData[j] = arg.charCodeAt(j); + javaArgData.setUint16(j * 2, arg.charCodeAt(j), true); } - javaArgsData[i] = javaArg; + javaArgsData.setInt32(i * 4, javaArg, true); } - resolve(wrapExport(mainFunction, teavm.instance)(javaArgs)); + controller.resolve = resolve; + controller.reject = reject; + wrapExport(teavm.instance.exports.start, teavm.instance)(javaArgs); + process(controller); }); } } - return { JavaError, importDefaults, load, wrapExport, createTeaVM, createMain }; + return { JavaError, load, create }; }(); diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java index 026d6b99e..8c3802c0f 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -500,7 +500,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { runs.add(createTestRun(configuration, testPath, child, RunKind.WASM, reference.toString(), notifier, onSuccess)); File htmlPath = getOutputFile(outputPathForMethod, "test-wasm", configuration.getSuffix(), false, ".html"); - properties.put("SCRIPT", "../" + testPath.getName() + "-runtime.js"); + properties.put("SCRIPT", "../" + testPath.getName()); properties.put("IDENTIFIER", reference.toString()); try { resourceToFile("teavm-run-test-wasm.html", htmlPath, properties); @@ -555,8 +555,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { if (run != null) { runs.add(run); - File testPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, - ".wasm-runtime.js"); + File testPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, ".wasm"); File htmlPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, ".html"); properties.put("SCRIPT", testPath.getName()); properties.put("IDENTIFIER", ""); diff --git a/tools/junit/src/main/resources/teavm-run-test-wasm.html b/tools/junit/src/main/resources/teavm-run-test-wasm.html index 4cf3158b7..ff4cbc7df 100644 --- a/tools/junit/src/main/resources/teavm-run-test-wasm.html +++ b/tools/junit/src/main/resources/teavm-run-test-wasm.html @@ -21,9 +21,9 @@ - + \ No newline at end of file