mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
wasm: trying to implement coroutines
This commit is contained in:
parent
c4c1408160
commit
bd53c1a5a2
|
@ -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<? 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 };
|
||||
}();
|
||||
|
|
|
@ -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", "");
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="${SCRIPT}"></script>
|
||||
<script type="text/javascript" src="${SCRIPT}-runtime.js"></script>
|
||||
<script type="text/javascript">
|
||||
TeaVM.wasm.load("test.wasm").then(teavm => teavm.main(["${IDENTIFIER}"]));
|
||||
TeaVM.wasm.load("${SCRIPT}").then(teavm => teavm.main(["${IDENTIFIER}"]));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user