wasm: trying to implement coroutines

This commit is contained in:
Alexey Andreev 2021-03-21 22:13:09 +03:00
parent c4c1408160
commit bd53c1a5a2
6 changed files with 206 additions and 64 deletions

View File

@ -27,9 +27,11 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import org.teavm.ast.InvocationExpr;
import org.teavm.ast.decompilation.Decompiler; import org.teavm.ast.decompilation.Decompiler;
import org.teavm.backend.lowlevel.analyze.LowLevelInliningFilterFactory; import org.teavm.backend.lowlevel.analyze.LowLevelInliningFilterFactory;
import org.teavm.backend.lowlevel.dependency.StringsDependencyListener; 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.ShadowStackIntrinsic;
import org.teavm.backend.wasm.intrinsics.StructureIntrinsic; import org.teavm.backend.wasm.intrinsics.StructureIntrinsic;
import org.teavm.backend.wasm.intrinsics.WasmHeapIntrinsic; 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.WasmIntrinsicFactory;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsicFactoryContext; 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.intrinsics.WasmRuntimeIntrinsic;
import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmLocal; 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.WasmReturn;
import org.teavm.backend.wasm.model.expression.WasmSetLocal; import org.teavm.backend.wasm.model.expression.WasmSetLocal;
import org.teavm.backend.wasm.model.expression.WasmStoreInt32; 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.optimization.UnusedFunctionElimination;
import org.teavm.backend.wasm.render.WasmBinaryRenderer; import org.teavm.backend.wasm.render.WasmBinaryRenderer;
import org.teavm.backend.wasm.render.WasmBinaryVersion; 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.ClassPatch;
import org.teavm.model.transformation.NullCheckInsertion; import org.teavm.model.transformation.NullCheckInsertion;
import org.teavm.runtime.Allocator; import org.teavm.runtime.Allocator;
import org.teavm.runtime.EventQueue;
import org.teavm.runtime.ExceptionHandling; import org.teavm.runtime.ExceptionHandling;
import org.teavm.runtime.Fiber;
import org.teavm.runtime.RuntimeArray; import org.teavm.runtime.RuntimeArray;
import org.teavm.runtime.RuntimeClass; import org.teavm.runtime.RuntimeClass;
import org.teavm.runtime.RuntimeObject; 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()); dependencyAnalyzer.addDependencyListener(new StringsDependencyListener());
} }
@ -417,6 +440,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
context.addIntrinsic(new MemoryTraceIntrinsic()); context.addIntrinsic(new MemoryTraceIntrinsic());
} }
context.addIntrinsic(new WasmHeapIntrinsic()); context.addIntrinsic(new WasmHeapIntrinsic());
context.addIntrinsic(new FiberIntrinsic());
IntrinsicFactoryContext intrinsicFactoryContext = new IntrinsicFactoryContext(); IntrinsicFactoryContext intrinsicFactoryContext = new IntrinsicFactoryContext();
for (WasmIntrinsicFactory additionalIntrinsicFactory : additionalIntrinsics) { for (WasmIntrinsicFactory additionalIntrinsicFactory : additionalIntrinsics) {
@ -459,15 +483,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
generateInitFunction(classes, initFunction, names, binaryWriter.getAddress()); generateInitFunction(classes, initFunction, names, binaryWriter.getAddress());
module.add(initFunction); module.add(initFunction);
module.setStartFunction(initFunction); module.setStartFunction(initFunction);
module.add(createStartFunction(names));
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());
}
}
for (String functionName : classGenerator.getFunctionTable()) { for (String functionName : classGenerator.getFunctionTable()) {
WasmFunction function = module.getFunctions().get(functionName); WasmFunction function = module.getFunctions().get(functionName);
@ -503,6 +519,22 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
emitRuntime(buildTarget, getBaseName(outputName) + ".wasm-runtime.js"); 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 { private class IntrinsicFactoryContext implements WasmIntrinsicFactoryContext {
@Override @Override
public ClassReaderSource getClassSource() { public ClassReaderSource getClassSource() {
@ -967,4 +999,53 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
return classSource; 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<? extends TeaVMEntryPoint> 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();
}
}
}
} }

View File

@ -701,7 +701,7 @@ class WasmCRenderingVisitor implements WasmExpressionVisitor {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (expression.isImported()) { if (expression.isImported()) {
sb.append(!function.getImportModule().isEmpty() sb.append(function.getImportModule() != null && !function.getImportModule().isEmpty()
? function.getImportModule() + "_" + function.getImportName() ? function.getImportModule() + "_" + function.getImportName()
: function.getImportName()); : function.getImportName());
} else { } else {

View File

@ -17,10 +17,9 @@ package org.teavm.runtime;
import java.util.Arrays; import java.util.Arrays;
import org.teavm.backend.c.intrinsic.RuntimeInclude; import org.teavm.backend.c.intrinsic.RuntimeInclude;
import org.teavm.interop.Export;
import org.teavm.interop.Import; import org.teavm.interop.Import;
import org.teavm.interop.Platforms;
import org.teavm.interop.StaticInit; import org.teavm.interop.StaticInit;
import org.teavm.interop.UnsupportedOn;
@StaticInit @StaticInit
public final class EventQueue { 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() { public static void stop() {
finished = true; finished = true;
} }
@ -129,12 +153,10 @@ public final class EventQueue {
@Import(name = "teavm_waitFor") @Import(name = "teavm_waitFor")
@RuntimeInclude("fiber.h") @RuntimeInclude("fiber.h")
@UnsupportedOn(Platforms.WEBASSEMBLY)
private static native void waitFor(long time); private static native void waitFor(long time);
@Import(name = "teavm_interrupt") @Import(name = "teavm_interrupt", module = "teavm")
@RuntimeInclude("fiber.h") @RuntimeInclude("fiber.h")
@UnsupportedOn(Platforms.WEBASSEMBLY)
private static native void interrupt(); private static native void interrupt();
private static void ensureCapacity(int capacity) { private static void ensureCapacity(int capacity) {

View File

@ -43,19 +43,46 @@ TeaVM.wasm = function() {
function getNativeOffset(instant) { function getNativeOffset(instant) {
return new Date(instant).getTimezoneOffset(); return new Date(instant).getTimezoneOffset();
} }
function logString(string) { function logString(string, controller) {
var memory = new DataView(logString.memory.buffer); let instance = controller.instance;
var arrayPtr = memory.getUint32(string + 8, true); let memory = instance.exports.memory.buffer;
var length = memory.getUint32(arrayPtr + 8, true); let arrayPtr = instance.exports.teavm_stringData(string);
for (var i = 0; i < length; ++i) { let length = instance.exports.teavm_arrayLength(arrayPtr);
putwchar(memory.getUint16(i * 2 + arrayPtr + 12, true)); 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) { function logInt(i) {
lineBuffer += i.toString(); 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 = { obj.teavm = {
currentTimeMillis: currentTimeMillis, currentTimeMillis: currentTimeMillis,
nanoTime: function() { return performance.now(); }, nanoTime: function() { return performance.now(); },
@ -67,9 +94,10 @@ TeaVM.wasm = function() {
towlower: towlower, towlower: towlower,
towupper: towupper, towupper: towupper,
getNativeOffset: getNativeOffset, getNativeOffset: getNativeOffset,
logString: logString, logString: string => logString(string, controller),
logInt: logInt, logInt: logInt,
logOutOfMemory: function() { console.log("Out of memory") } logOutOfMemory: () => console.log("Out of memory"),
teavm_interrupt: () => interrupt(controller)
}; };
obj.teavmMath = Math; obj.teavmMath = Math;
@ -91,6 +119,8 @@ TeaVM.wasm = function() {
gcCompleted: function() {}, gcCompleted: function() {},
init: function(maxHeap) {} init: function(maxHeap) {}
}; };
return controller;
} }
function createTeaVM(instance) { function createTeaVM(instance) {
@ -109,35 +139,29 @@ TeaVM.wasm = function() {
} }
teavm.main = createMain(teavm, instance.exports.main); teavm.main = createMain(teavm, instance.exports.main);
return teavm; return teavm;
} }
function wrapExport(fn, instance) { function wrapExport(fn, instance) {
return function() { return function() {
let result = fn.apply(this, arguments); let result = fn.apply(this, arguments);
let ex = instance.exports.teavm_catchException(); let ex = catchException(instance);
if (ex !== 0) { if (ex !== null) {
throw new JavaError("Uncaught exception occurred in java"); throw ex;
} }
return result; 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) { 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(); let xhr = new XMLHttpRequest();
xhr.responseType = "arraybuffer"; xhr.responseType = "arraybuffer";
xhr.open("GET", path); xhr.open("GET", path);
@ -146,45 +170,61 @@ TeaVM.wasm = function() {
xhr.onload = () => { xhr.onload = () => {
let response = xhr.response; let response = xhr.response;
if (!response) { if (!response) {
reject("Error loading Wasm data")
return; return;
} }
WebAssembly.instantiate(response, importObj).then(resultObject => { resolve(response);
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);
});
}; };
xhr.send(); 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) { return function(args) {
if (typeof args === "undefined") { if (typeof args === "undefined") {
args = []; args = [];
} }
return new Promise(resolve => { return new Promise((resolve, reject) => {
let javaArgs = teavm.allocateStringArray(mainArgs.length); let javaArgs = teavm.allocateStringArray(args.length);
let javaArgsData = new Uint32Array(teavm.memory, teavm.objectArrayData(javaArgs), args.length); let javaArgsData = new DataView(teavm.memory.buffer, teavm.objectArrayData(javaArgs), args.length * 4);
for (let i = 0; i < mainArgs.length; ++i) { for (let i = 0; i < args.length; ++i) {
let arg = args[i]; let arg = args[i];
let javaArg = teavm.allocateString(arg.length); let javaArg = teavm.allocateString(arg.length);
let javaArgAddress = teavm.objectArrayData(teavm.stringData(javaArg)); 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) { 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 };
}(); }();

View File

@ -500,7 +500,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
runs.add(createTestRun(configuration, testPath, child, RunKind.WASM, reference.toString(), runs.add(createTestRun(configuration, testPath, child, RunKind.WASM, reference.toString(),
notifier, onSuccess)); notifier, onSuccess));
File htmlPath = getOutputFile(outputPathForMethod, "test-wasm", configuration.getSuffix(), false, ".html"); 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()); properties.put("IDENTIFIER", reference.toString());
try { try {
resourceToFile("teavm-run-test-wasm.html", htmlPath, properties); resourceToFile("teavm-run-test-wasm.html", htmlPath, properties);
@ -555,8 +555,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
if (run != null) { if (run != null) {
runs.add(run); runs.add(run);
File testPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, File testPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, ".wasm");
".wasm-runtime.js");
File htmlPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, ".html"); File htmlPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, ".html");
properties.put("SCRIPT", testPath.getName()); properties.put("SCRIPT", testPath.getName());
properties.put("IDENTIFIER", ""); properties.put("IDENTIFIER", "");

View File

@ -21,9 +21,9 @@
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head> </head>
<body> <body>
<script type="text/javascript" src="${SCRIPT}"></script> <script type="text/javascript" src="${SCRIPT}-runtime.js"></script>
<script type="text/javascript"> <script type="text/javascript">
TeaVM.wasm.load("test.wasm").then(teavm => teavm.main(["${IDENTIFIER}"])); TeaVM.wasm.load("${SCRIPT}").then(teavm => teavm.main(["${IDENTIFIER}"]));
</script> </script>
</body> </body>
</html> </html>