diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java index a8977b063..46315f25f 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java @@ -15,21 +15,24 @@ */ package org.teavm.classlib.java.lang; +import org.teavm.classlib.PlatformDetector; import org.teavm.dependency.PluggableDependency; import org.teavm.interop.Address; import org.teavm.interop.Async; +import org.teavm.interop.AsyncCallback; import org.teavm.interop.DelegateTo; import org.teavm.interop.Rename; import org.teavm.interop.Structure; import org.teavm.interop.Superclass; import org.teavm.interop.Sync; +import org.teavm.interop.Unmanaged; import org.teavm.jso.browser.TimerHandler; import org.teavm.platform.Platform; import org.teavm.platform.PlatformObject; import org.teavm.platform.PlatformQueue; import org.teavm.platform.PlatformRunnable; -import org.teavm.platform.async.AsyncCallback; import org.teavm.runtime.Allocator; +import org.teavm.runtime.EventQueue; import org.teavm.runtime.RuntimeArray; import org.teavm.runtime.RuntimeClass; import org.teavm.runtime.RuntimeObject; @@ -39,25 +42,26 @@ public class TObject { Monitor monitor; static class Monitor { + static final int MASK = 0x80000000; + PlatformQueue enteringThreads; PlatformQueue notifyListeners; TThread owner; int count; + int id; - public Monitor() { + Monitor() { this.owner = TThread.currentThread(); - enteringThreads = Platform.createQueue(); - notifyListeners = Platform.createQueue(); } } - interface NotifyListener extends PlatformRunnable { + interface NotifyListener extends PlatformRunnable, EventQueue.Event { boolean expired(); } static void monitorEnterSync(TObject o) { if (o.monitor == null) { - o.monitor = new Monitor(); + createMonitor(o); } if (o.monitor.owner == null) { o.monitor.owner = TThread.currentThread(); @@ -83,7 +87,7 @@ public class TObject { static void monitorEnter(TObject o, int count) { if (o.monitor == null) { - o.monitor = new Monitor(); + createMonitor(o); } if (o.monitor.owner == null) { o.monitor.owner = TThread.currentThread(); @@ -95,13 +99,23 @@ public class TObject { } } + private static void createMonitor(TObject o) { + if (PlatformDetector.isLowLevel()) { + int hashCode = hashCodeLowLevel(o); + o.monitor = new Monitor(); + o.monitor.id = hashCode; + } else { + o.monitor = new Monitor(); + } + } + @Async static native void monitorEnterWait(TObject o, int count); - static void monitorEnterWait(final TObject o, final int count, final AsyncCallback callback) { - final TThread thread = TThread.currentThread(); + static void monitorEnterWait(TObject o, int count, AsyncCallback callback) { + TThread thread = TThread.currentThread(); if (o.monitor == null) { - o.monitor = new Monitor(); + createMonitor(o); TThread.setCurrentThread(thread); o.monitor.count += count; callback.complete(null); @@ -113,7 +127,12 @@ public class TObject { callback.complete(null); return; } - o.monitor.enteringThreads.add(() -> { + + Monitor monitor = o.monitor; + if (monitor.enteringThreads == null) { + monitor.enteringThreads = Platform.createQueue(); + } + monitor.enteringThreads.add(() -> { TThread.setCurrentThread(thread); o.monitor.owner = thread; o.monitor.count += count; @@ -122,47 +141,73 @@ public class TObject { } @Sync - static void monitorExit(final TObject o) { + static void monitorExit(TObject o) { monitorExit(o, 1); } @Sync - static void monitorExit(final TObject o, int count) { + static void monitorExit(TObject o, int count) { if (o.isEmptyMonitor() || o.monitor.owner != TThread.currentThread()) { throw new TIllegalMonitorStateException(); } - o.monitor.count -= count; - if (o.monitor.count > 0) { + + Monitor monitor = o.monitor; + monitor.count -= count; + if (monitor.count > 0) { return; } - o.monitor.owner = null; - if (!o.monitor.enteringThreads.isEmpty()) { - Platform.postpone(() -> { - if (o.isEmptyMonitor() || o.monitor.owner != null) { - return; - } - if (!o.monitor.enteringThreads.isEmpty()) { - o.monitor.enteringThreads.remove().run(); - } - }); + monitor.owner = null; + if (monitor.enteringThreads != null && !monitor.enteringThreads.isEmpty()) { + if (PlatformDetector.isLowLevel()) { + EventQueue.offer(() -> waitForOtherThreads(o)); + } else { + Platform.postpone(() -> waitForOtherThreads(o)); + } } else { o.isEmptyMonitor(); } } + private static void waitForOtherThreads(TObject o) { + if (o.isEmptyMonitor() || o.monitor.owner != null) { + return; + } + Monitor monitor = o.monitor; + if (monitor.enteringThreads != null && !monitor.enteringThreads.isEmpty()) { + PlatformQueue enteringThreads = monitor.enteringThreads; + PlatformRunnable r = enteringThreads.remove(); + if (enteringThreads == null) { + monitor.enteringThreads = null; + } + r.run(); + } + } + boolean isEmptyMonitor() { + Monitor monitor = this.monitor; if (monitor == null) { return true; } - if (monitor.owner == null && monitor.enteringThreads.isEmpty() && monitor.notifyListeners.isEmpty()) { - monitor = null; + if (monitor.owner == null + && (monitor.enteringThreads == null || monitor.enteringThreads.isEmpty()) + && (monitor.notifyListeners == null || monitor.notifyListeners.isEmpty())) { + deleteMonitor(); return true; } else { return false; } } + private void deleteMonitor() { + if (PlatformDetector.isLowLevel()) { + int id = monitor.id; + setHashCodeLowLevel(this, id); + } else { + monitor = null; + } + } + static boolean holdsLock(TObject o) { return o.monitor != null && o.monitor.owner == TThread.currentThread(); } @@ -200,8 +245,25 @@ public class TObject { return getClass().getName() + "@" + TInteger.toHexString(identity()); } - @DelegateTo("identityLowLevel") int identity() { + if (PlatformDetector.isLowLevel()) { + Monitor monitor = this.monitor; + if (monitor == null) { + int hashCode = hashCodeLowLevel(this); + if (hashCode == 0) { + hashCode = identityLowLevel(); + setHashCodeLowLevel(this, hashCode); + } + return hashCode; + } else { + int hashCode = monitor.id; + if (hashCode == 0) { + hashCode = identityLowLevel(); + monitor.id = hashCode; + } + return hashCode; + } + } PlatformObject platformThis = Platform.getPlatformObject(this); if (platformThis.getId() == 0) { platformThis.setId(Platform.nextObjectId()); @@ -209,19 +271,48 @@ public class TObject { return Platform.getPlatformObject(this).getId(); } - @SuppressWarnings("unused") - private static int identityLowLevel(RuntimeObject object) { - int result = object.hashCode; + @DelegateTo("hashCodeLowLevelImpl") + private static native int hashCodeLowLevel(TObject obj); + + @Unmanaged + private static int hashCodeLowLevelImpl(RuntimeObject obj) { + return obj.hashCode; + } + + @DelegateTo("setHashCodeLowLevelImpl") + private static native void setHashCodeLowLevel(TObject obj, int value); + + @Unmanaged + private static void setHashCodeLowLevelImpl(RuntimeObject obj, int value) { + obj.hashCode = value; + } + + @Unmanaged + private static int identityLowLevel() { + int result = RuntimeObject.nextId++; if (result == 0) { result = RuntimeObject.nextId++; - if (result == 0) { - result = RuntimeObject.nextId++; + if (result == Monitor.MASK) { + result = 1; } - object.hashCode = result; } return result; } + @DelegateTo("identityOrMonitorLowLevel") + private native int identityOrMonitor(); + + private static int identityOrMonitorLowLevel(RuntimeObject object) { + return object.hashCode; + } + + @DelegateTo("setIdentityLowLevel") + native void setIdentity(int id); + + private static void setIdentityLowLevel(RuntimeObject object, int id) { + object.hashCode = id; + } + @Override @DelegateTo("cloneLowLevel") @PluggableDependency(ObjectDependencyPlugin.class) @@ -264,13 +355,23 @@ public class TObject { throw new TIllegalMonitorStateException(); } PlatformQueue listeners = monitor.notifyListeners; + if (listeners == null) { + return; + } while (!listeners.isEmpty()) { NotifyListener listener = listeners.remove(); if (!listener.expired()) { - Platform.postpone(listener); + if (PlatformDetector.isLowLevel()) { + EventQueue.offer(listener); + } else { + Platform.postpone(listener); + } break; } } + if (listeners.isEmpty()) { + monitor.notifyListeners = null; + } } @Sync @@ -280,12 +381,20 @@ public class TObject { throw new TIllegalMonitorStateException(); } PlatformQueue listeners = monitor.notifyListeners; + if (listeners == null) { + return; + } while (!listeners.isEmpty()) { NotifyListener listener = listeners.remove(); if (!listener.expired()) { - Platform.postpone(listener); + if (PlatformDetector.isLowLevel()) { + EventQueue.offer(listener); + } else { + Platform.postpone(listener); + } } } + monitor.notifyListeners = null; } @Rename("wait") @@ -309,17 +418,23 @@ public class TObject { private native void waitImpl(long timeout, int nanos) throws TInterruptedException; public final void waitImpl(long timeout, int nanos, AsyncCallback callback) { + Monitor monitor = this.monitor; final NotifyListenerImpl listener = new NotifyListenerImpl(this, callback, monitor.count); + if (monitor.notifyListeners == null) { + monitor.notifyListeners = Platform.createQueue(); + } monitor.notifyListeners.add(listener); TThread.currentThread().interruptHandler = listener; if (timeout > 0 || nanos > 0) { - listener.timerId = Platform.schedule(listener, timeout >= Integer.MAX_VALUE ? Integer.MAX_VALUE - : (int) timeout); + int timeoutToSchedule = timeout >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) timeout; + listener.timerId = PlatformDetector.isLowLevel() + ? EventQueue.offer(listener, timeoutToSchedule + System.currentTimeMillis()) + : Platform.schedule(listener, timeoutToSchedule); } monitorExit(this, monitor.count); } - private static class NotifyListenerImpl implements NotifyListener, TimerHandler, PlatformRunnable, + static class NotifyListenerImpl implements NotifyListener, TimerHandler, PlatformRunnable, TThreadInterruptHandler { final TObject obj; final AsyncCallback callback; @@ -329,7 +444,7 @@ public class TObject { boolean performed; int lockCount; - public NotifyListenerImpl(TObject obj, AsyncCallback callback, int lockCount) { + NotifyListenerImpl(TObject obj, AsyncCallback callback, int lockCount) { this.obj = obj; this.callback = callback; this.lockCount = lockCount; @@ -344,11 +459,19 @@ public class TObject { @Override public void onTimer() { - Platform.postpone(() -> { - if (!expired()) { - run(); - } - }); + if (PlatformDetector.isLowLevel()) { + EventQueue.offer(() -> { + if (!expired()) { + run(); + } + }); + } else { + Platform.postpone(() -> { + if (!expired()) { + run(); + } + }); + } } @Override @@ -358,7 +481,11 @@ public class TObject { } performed = true; if (timerId >= 0) { - Platform.killSchedule(timerId); + if (PlatformDetector.isLowLevel()) { + EventQueue.kill(timerId); + } else { + Platform.killSchedule(timerId); + } timerId = -1; } TThread.setCurrentThread(currentThread); @@ -372,10 +499,18 @@ public class TObject { } performed = true; if (timerId >= 0) { - Platform.killSchedule(timerId); + if (PlatformDetector.isLowLevel()) { + EventQueue.kill(timerId); + } else { + Platform.killSchedule(timerId); + } timerId = -1; } - Platform.postpone(() -> callback.error(new TInterruptedException())); + if (PlatformDetector.isLowLevel()) { + EventQueue.offer(() -> callback.error(new TInterruptedException())); + } else { + Platform.postpone(() -> callback.error(new TInterruptedException())); + } } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java index 2e6fcf75f..670509fd6 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java @@ -15,10 +15,13 @@ */ package org.teavm.classlib.java.lang; +import org.teavm.classlib.PlatformDetector; import org.teavm.interop.Async; +import org.teavm.interop.AsyncCallback; import org.teavm.platform.Platform; import org.teavm.platform.PlatformRunnable; -import org.teavm.platform.async.AsyncCallback; +import org.teavm.runtime.EventQueue; +import org.teavm.runtime.Fiber; public class TThread extends TObject implements TRunnable { private static TThread mainThread = new TThread("main"); @@ -56,20 +59,26 @@ public class TThread extends TObject implements TRunnable { } public void start() { - Platform.startThread(() -> { - try { - activeCount++; - setCurrentThread(TThread.this); - TThread.this.run(); - } finally { - synchronized (finishedLock) { - finishedLock.notifyAll(); - } - alive = false; - activeCount--; - setCurrentThread(mainThread); + if (PlatformDetector.isLowLevel()) { + EventQueue.offer(() -> Fiber.start(this::runThread)); + } else { + Platform.startThread(this::runThread); + } + } + + private void runThread() { + try { + activeCount++; + setCurrentThread(TThread.this); + TThread.this.run(); + } finally { + synchronized (finishedLock) { + finishedLock.notifyAll(); } - }); + alive = false; + activeCount--; + setCurrentThread(mainThread); + } } static void setCurrentThread(TThread thread) { @@ -130,10 +139,17 @@ public class TThread extends TObject implements TRunnable { static native void switchContext(TThread thread); private static void switchContext(final TThread thread, final AsyncCallback callback) { - Platform.postpone(() -> { - setCurrentThread(thread); - callback.complete(null); - }); + if (PlatformDetector.isLowLevel()) { + EventQueue.offer(() -> { + setCurrentThread(thread); + callback.complete(null); + }); + } else { + Platform.postpone(() -> { + setCurrentThread(thread); + callback.complete(null); + }); + } } public void interrupt() { @@ -176,19 +192,24 @@ public class TThread extends TObject implements TRunnable { private static void sleep(long millis, final AsyncCallback callback) { final TThread current = currentThread(); - int intMillis = millis < Integer.MAX_VALUE ? (int) millis : Integer.MAX_VALUE; SleepHandler handler = new SleepHandler(current, callback); - handler.scheduleId = Platform.schedule(handler, intMillis); - current.interruptHandler = handler; + if (PlatformDetector.isLowLevel()) { + handler.scheduleId = EventQueue.offer(handler, System.currentTimeMillis() + millis); + current.interruptHandler = handler; + } else { + int intMillis = millis < Integer.MAX_VALUE ? (int) millis : Integer.MAX_VALUE; + handler.scheduleId = Platform.schedule(handler, intMillis); + current.interruptHandler = handler; + } } - private static class SleepHandler implements PlatformRunnable, TThreadInterruptHandler { + static class SleepHandler implements PlatformRunnable, EventQueue.Event, TThreadInterruptHandler { private TThread thread; private AsyncCallback callback; private boolean isInterrupted; int scheduleId; - public SleepHandler(TThread thread, AsyncCallback callback) { + SleepHandler(TThread thread, AsyncCallback callback) { this.thread = thread; this.callback = callback; } @@ -197,8 +218,13 @@ public class TThread extends TObject implements TRunnable { public void interrupted() { thread.interruptedFlag = false; isInterrupted = true; - Platform.killSchedule(scheduleId); - Platform.postpone(() -> callback.error(new TInterruptedException())); + if (PlatformDetector.isLowLevel()) { + EventQueue.kill(scheduleId); + EventQueue.offer(() -> callback.error(new TInterruptedException())); + } else { + Platform.killSchedule(scheduleId); + Platform.postpone(() -> callback.error(new TInterruptedException())); + } } @Override diff --git a/classlib/src/main/java/org/teavm/classlib/java/net/impl/TXHRURLConnection.java b/classlib/src/main/java/org/teavm/classlib/java/net/impl/TXHRURLConnection.java index f5e29e53e..ae408e2d4 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/net/impl/TXHRURLConnection.java +++ b/classlib/src/main/java/org/teavm/classlib/java/net/impl/TXHRURLConnection.java @@ -27,10 +27,10 @@ import java.util.Map; import org.teavm.classlib.java.net.THttpURLConnection; import org.teavm.classlib.java.net.TURL; import org.teavm.interop.Async; +import org.teavm.interop.AsyncCallback; import org.teavm.jso.ajax.XMLHttpRequest; import org.teavm.jso.typedarrays.ArrayBuffer; import org.teavm.jso.typedarrays.Int8Array; -import org.teavm.platform.async.AsyncCallback; public class TXHRURLConnection extends THttpURLConnection { private XMLHttpRequest xhr; diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TArrayBlockingQueue.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TArrayBlockingQueue.java index 4d0677c74..5f552d8bc 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TArrayBlockingQueue.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TArrayBlockingQueue.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.NoSuchElementException; import java.util.Objects; +import org.teavm.classlib.PlatformDetector; import org.teavm.classlib.java.lang.TInterruptedException; import org.teavm.classlib.java.lang.TThread; import org.teavm.classlib.java.lang.TThreadInterruptHandler; @@ -27,11 +28,12 @@ import org.teavm.classlib.java.util.TAbstractQueue; import org.teavm.classlib.java.util.TCollection; import org.teavm.classlib.java.util.TIterator; import org.teavm.interop.Async; +import org.teavm.interop.AsyncCallback; import org.teavm.interop.Sync; import org.teavm.platform.Platform; import org.teavm.platform.PlatformQueue; import org.teavm.platform.PlatformRunnable; -import org.teavm.platform.async.AsyncCallback; +import org.teavm.runtime.EventQueue; public class TArrayBlockingQueue extends TAbstractQueue implements TBlockingQueue { private Object[] array; @@ -418,7 +420,11 @@ public class TArrayBlockingQueue extends TAbstractQueue implements TBlocki if (waitHandlers != null) { while (!waitHandlers.isEmpty()) { WaitHandler handler = waitHandlers.remove(); - Platform.postpone(() -> handler.changed()); + if (PlatformDetector.isLowLevel()) { + EventQueue.offer(handler::changed); + } else { + Platform.postpone(handler::changed); + } } waitHandlers = null; } @@ -436,7 +442,9 @@ public class TArrayBlockingQueue extends TAbstractQueue implements TBlocki waitHandlers.add(handler); if (timeLimit > 0) { int timeout = Math.max(0, (int) (timeLimit - System.currentTimeMillis())); - handler.timerId = Platform.schedule(handler, timeout); + handler.timerId = PlatformDetector.isLowLevel() + ? EventQueue.offer(handler, timeLimit) + : Platform.schedule(handler, timeout); } else { handler.timerId = -1; } @@ -444,7 +452,7 @@ public class TArrayBlockingQueue extends TAbstractQueue implements TBlocki TThread.currentThread().interruptHandler = handler; } - class WaitHandler implements PlatformRunnable, TThreadInterruptHandler { + class WaitHandler implements PlatformRunnable, TThreadInterruptHandler, EventQueue.Event { AsyncCallback callback; boolean complete; int timerId; @@ -475,7 +483,11 @@ public class TArrayBlockingQueue extends TAbstractQueue implements TBlocki } complete = true; if (timerId >= 0) { - Platform.killSchedule(timerId); + if (PlatformDetector.isLowLevel()) { + EventQueue.kill(timerId); + } else { + Platform.killSchedule(timerId); + } timerId = -1; } TThread.currentThread().interruptHandler = null; diff --git a/core/src/main/java/org/teavm/backend/c/CTarget.java b/core/src/main/java/org/teavm/backend/c/CTarget.java index f45eadde2..3ed182e76 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -33,6 +33,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import org.teavm.ast.InvocationExpr; import org.teavm.ast.decompilation.Decompiler; import org.teavm.backend.c.analyze.CDependencyListener; import org.teavm.backend.c.generate.BufferedCodeWriter; @@ -51,6 +52,7 @@ import org.teavm.backend.c.intrinsic.FunctionIntrinsic; import org.teavm.backend.c.intrinsic.GCIntrinsic; import org.teavm.backend.c.intrinsic.IntegerIntrinsic; import org.teavm.backend.c.intrinsic.Intrinsic; +import org.teavm.backend.c.intrinsic.IntrinsicContext; import org.teavm.backend.c.intrinsic.IntrinsicFactory; import org.teavm.backend.c.intrinsic.LongIntrinsic; import org.teavm.backend.c.intrinsic.MutatorIntrinsic; @@ -61,6 +63,7 @@ import org.teavm.backend.c.intrinsic.PlatformObjectIntrinsic; import org.teavm.backend.c.intrinsic.RuntimeClassIntrinsic; import org.teavm.backend.c.intrinsic.ShadowStackIntrinsic; import org.teavm.backend.c.intrinsic.StructureIntrinsic; +import org.teavm.backend.lowlevel.transform.CoroutineTransformation; import org.teavm.dependency.ClassDependency; import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyListener; @@ -96,8 +99,11 @@ import org.teavm.model.lowlevel.NullCheckTransformation; import org.teavm.model.lowlevel.ShadowStackTransformer; import org.teavm.model.transformation.ClassInitializerInsertionTransformer; import org.teavm.model.transformation.ClassPatch; +import org.teavm.model.util.AsyncMethodFinder; 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; @@ -121,6 +127,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { private ExportDependencyListener exportDependencyListener = new ExportDependencyListener(); private int minHeapSize = 32 * 1024 * 1024; private List intrinsicFactories = new ArrayList<>(); + private Set asyncMethods; public void setMinHeapSize(int minHeapSize) { this.minHeapSize = minHeapSize; @@ -201,6 +208,30 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { dependencyAnalyzer.linkField(field.getReference()); } } + + 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", void.class)).use(); + dependencyAnalyzer.linkMethod(new MethodReference(EventQueue.class, "process", void.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(); + } + } + } + + @Override + public void analyzeBeforeOptimizations(ListableClassReaderSource classSource) { + AsyncMethodFinder asyncFinder = new AsyncMethodFinder(controller.getDependencyInfo().getCallGraph(), + controller.getDiagnostics()); + asyncFinder.find(classSource); + asyncMethods = new HashSet<>(asyncFinder.getAsyncMethods()); + asyncMethods.addAll(asyncFinder.getAsyncFamilyMethods()); } @Override @@ -214,6 +245,8 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { classInitializerEliminator.apply(program); classInitializerTransformer.transform(program); nullCheckTransformation.apply(program, method.getResultType()); + new CoroutineTransformation(controller.getUnprocessedClassSource(), asyncMethods) + .apply(program, method.getReference()); shadowStackTransformer.apply(program, method); } @@ -242,6 +275,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { intrinsics.add(new ExceptionHandlingIntrinsic()); intrinsics.add(new FunctionIntrinsic(characteristics, exportDependencyListener.getResolvedMethods())); intrinsics.add(new RuntimeClassIntrinsic()); + intrinsics.add(new FiberIntrinsic()); intrinsics.add(new LongIntrinsic()); intrinsics.add(new IntegerIntrinsic()); @@ -250,7 +284,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { GenerationContext context = new GenerationContext(vtableProvider, characteristics, controller.getDependencyInfo(), stringPool, nameProvider, controller.getDiagnostics(), classes, - intrinsics, generators); + intrinsics, generators, asyncMethods::contains); BufferedCodeWriter codeWriter = new BufferedCodeWriter(); copyResource(codeWriter, "runtime.c"); @@ -425,7 +459,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { writer.println("initStaticFields();"); generateStaticInitializerCalls(context, writer, classes); writer.println(context.getNames().forClassInitializer("java.lang.String") + "();"); - generateCallToMainMethod(context, writer); + generateFiberStart(context, writer); writer.outdent().println("}"); } @@ -481,10 +515,49 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { writer.outdent().println("}"); } - private void generateCallToMainMethod(GenerationContext context, CodeWriter writer) { + private void generateFiberStart(GenerationContext context, CodeWriter writer) { + String startName = context.getNames().forMethod(new MethodReference(Fiber.class, "startMain", void.class)); + String processName = context.getNames().forMethod(new MethodReference(EventQueue.class, "process", void.class)); + writer.println(startName + "();"); + writer.println(processName + "();"); + } + + class FiberIntrinsic implements Intrinsic { + @Override + public boolean canHandle(MethodReference method) { + if (!method.getClassName().equals(Fiber.class.getName())) { + return false; + } + switch (method.getName()) { + case "runMain": + case "setCurrentThread": + return true; + default: + return false; + } + } + + @Override + public void apply(IntrinsicContext context, InvocationExpr invocation) { + switch (invocation.getMethod().getName()) { + case "runMain": + generateCallToMainMethod(context.names(), context.writer()); + break; + case "setCurrentThread": + String methodName = context.names().forMethod(new MethodReference(Thread.class, + "setCurrentThread", Thread.class, void.class)); + context.writer().print(methodName).print("("); + context.emit(invocation.getArguments().get(0)); + context.writer().print(")"); + break; + } + } + } + + private void generateCallToMainMethod(NameProvider names, CodeWriter writer) { TeaVMEntryPoint entryPoint = controller.getEntryPoints().get("main"); if (entryPoint != null) { - String mainMethod = context.getNames().forMethod(entryPoint.getMethod()); + String mainMethod = names.forMethod(entryPoint.getMethod()); writer.println(mainMethod + "(NULL);"); } } @@ -496,6 +569,6 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { @Override public boolean isAsyncSupported() { - return false; + return true; } } diff --git a/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java b/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java index 878da78d7..9ea443d80 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java @@ -194,7 +194,6 @@ public class ClassGenerator { codeGenerator.generateMethodSignature(forwardDeclarationsWriter, method.getReference(), method.hasModifier(ElementModifier.STATIC), false); forwardDeclarationsWriter.println(";"); - } private void generateInitializer(ClassHolder cls) { diff --git a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java index 9b58b0c38..5222807fd 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java @@ -65,14 +65,17 @@ import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationValue; import org.teavm.model.ClassReader; import org.teavm.model.ElementModifier; +import org.teavm.model.FieldReference; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; import org.teavm.model.classes.VirtualTable; import org.teavm.runtime.Allocator; 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; public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { private static final MethodReference ALLOC_METHOD = new MethodReference(Allocator.class, @@ -83,6 +86,14 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { "allocateMultiArray", RuntimeClass.class, Address.class, int.class, RuntimeArray.class); private static final MethodReference THROW_EXCEPTION_METHOD = new MethodReference(ExceptionHandling.class, "throwException", Throwable.class, void.class); + private static final MethodReference MONITOR_ENTER = new MethodReference(Object.class, "monitorEnter", + Object.class, void.class); + private static final MethodReference MONITOR_EXIT = new MethodReference(Object.class, "monitorExit", + Object.class, void.class); + private static final MethodReference MONITOR_ENTER_SYNC = new MethodReference(Object.class, "monitorEnterSync", + Object.class, void.class); + private static final MethodReference MONITOR_EXIT_SYNC = new MethodReference(Object.class, "monitorExitSync", + Object.class, void.class); private GenerationContext context; private NameProvider names; @@ -91,6 +102,9 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { private int[] maxTemporaryVariableLevel = new int[5]; private MethodReference callingMethod; private Set includes; + private int currentPart; + private boolean end; + private boolean async; public CodeGenerationVisitor(GenerationContext context, CodeWriter writer, Set includes) { this.context = context; @@ -99,6 +113,10 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { this.includes = includes; } + public void setAsync(boolean async) { + this.async = async; + } + public int[] getTemporaries() { return maxTemporaryVariableLevel; } @@ -539,16 +557,31 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { @Override public void visit(QualificationExpr expr) { + FieldReference field = expr.getField(); + if (isMonitorField(field)) { + String tmp = allocTemporaryVariable(CVariableType.INT); + writer.print("(" + tmp + " = FIELD("); + expr.getQualified().acceptVisitor(this); + field = new FieldReference(RuntimeObject.class.getName(), "hashCode"); + writer.print(", ").print(names.forClass(field.getClassName()) + ", " + + names.forMemberField(field) + ")"); + writer.print(", UNPACK_MONITOR(" + tmp + "))"); + return; + } + if (expr.getQualified() != null) { writer.print("FIELD("); expr.getQualified().acceptVisitor(this); - writer.print(", ").print(names.forClass(expr.getField().getClassName()) + ", " - + names.forMemberField(expr.getField()) + ")"); + writer.print(", ").print(names.forClass(field.getClassName()) + ", " + names.forMemberField(field) + ")"); } else { - writer.print(names.forStaticField(expr.getField())); + writer.print(names.forStaticField(field)); } } + private boolean isMonitorField(FieldReference field) { + return field.getClassName().equals("java.lang.Object") && field.getFieldName().equals("monitor"); + } + @Override public void visit(NewExpr expr) { allocObject(expr.getConstructedClass()); @@ -630,11 +663,30 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { @Override public void visit(AssignmentStatement statement) { if (statement.getLeftValue() != null) { + if (statement.getLeftValue() instanceof QualificationExpr) { + QualificationExpr qualification = (QualificationExpr) statement.getLeftValue(); + FieldReference field = qualification.getField(); + if (isMonitorField(field)) { + writer.print("FIELD("); + qualification.getQualified().acceptVisitor(this); + field = new FieldReference(RuntimeObject.class.getName(), "hashCode"); + writer.print(", ").print(names.forClass(field.getClassName()) + ", " + + names.forMemberField(field) + ") = PACK_MONITOR("); + statement.getRightValue().acceptVisitor(this); + writer.println(");"); + return; + } + } + statement.getLeftValue().acceptVisitor(this); writer.print(" = "); } statement.getRightValue().acceptVisitor(this); writer.println(";"); + + if (statement.isAsync()) { + emitSuspendChecker(); + } } @Override @@ -643,9 +695,17 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { } private void visitMany(List statements) { - for (Statement statement : statements) { - statement.acceptVisitor(this); + if (statements.isEmpty()) { + return; } + boolean oldEnd = end; + for (int i = 0; i < statements.size() - 1; ++i) { + end = false; + statements.get(i).acceptVisitor(this); + } + end = oldEnd; + statements.get(statements.size() - 1).acceptVisitor(this); + end = oldEnd; } @Override @@ -688,8 +748,12 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { } writer.indent(); - visitMany(clause.getBody()); - writer.println("break;"); + boolean oldEnd = end; + for (Statement part : clause.getBody()) { + end = false; + part.acceptVisitor(this); + } + end = oldEnd; writer.outdent(); } @@ -716,7 +780,12 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { } writer.println(") {").indent(); - visitMany(statement.getBody()); + boolean oldEnd = end; + for (Statement part : statement.getBody()) { + end = false; + part.acceptVisitor(this); + } + end = oldEnd; if (statement.getId() != null) { writer.outdent().println("cnt_" + statement.getId() + ":;").indent(); @@ -779,7 +848,6 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { @Override public void visit(TryCatchStatement statement) { - } @Override @@ -788,10 +856,21 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { @Override public void visit(MonitorEnterStatement statement) { + writer.print(names.forMethod(async ? MONITOR_ENTER : MONITOR_ENTER_SYNC)).print("("); + statement.getObjectRef().acceptVisitor(this); + writer.println(");"); } @Override public void visit(MonitorExitStatement statement) { + writer.print(names.forMethod(async ? MONITOR_EXIT : MONITOR_EXIT_SYNC)).print("("); + statement.getObjectRef().acceptVisitor(this); + writer.println(");"); + } + + public void emitSuspendChecker() { + String suspendingName = names.forMethod(new MethodReference(Fiber.class, "isSuspending", boolean.class)); + writer.println("if (" + suspendingName + "(fiber)) goto exit_loop;"); } private IntrinsicContext intrinsicContext = new IntrinsicContext() { diff --git a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java index 116de0bad..701fd99b1 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java +++ b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java @@ -16,6 +16,7 @@ package org.teavm.backend.c.generate; import java.util.Set; +import org.teavm.ast.MethodNode; import org.teavm.ast.RegularMethodNode; import org.teavm.ast.VariableNode; import org.teavm.model.ElementModifier; @@ -43,16 +44,21 @@ public class CodeGenerator { writer.print(" {").indent().println(); localsWriter = writer.fragment(); - - CodeGenerationVisitor visitor = new CodeGenerationVisitor(context, writer, includes); - visitor.setCallingMethod(methodNode.getReference()); - methodNode.getBody().acceptVisitor(visitor); - + CodeGenerationVisitor visitor = generateMethodBody(methodNode); generateLocals(methodNode, visitor.getTemporaries()); writer.outdent().println("}"); } + + private CodeGenerationVisitor generateMethodBody(RegularMethodNode methodNode) { + CodeGenerationVisitor visitor = new CodeGenerationVisitor(context, writer, includes); + visitor.setAsync(context.isAsync(methodNode.getReference())); + visitor.setCallingMethod(methodNode.getReference()); + methodNode.getBody().acceptVisitor(visitor); + return visitor; + } + public void generateMethodSignature(CodeWriter writer, MethodReference methodRef, boolean isStatic, boolean withNames) { writer.print("static "); @@ -91,7 +97,7 @@ public class CodeGenerator { } } - private void generateLocals(RegularMethodNode methodNode, int[] temporaryCount) { + private void generateLocals(MethodNode methodNode, int[] temporaryCount) { int start = methodNode.getReference().parameterCount() + 1; for (int i = start; i < methodNode.getVariables().size(); ++i) { VariableNode variableNode = methodNode.getVariables().get(i); diff --git a/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java b/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java index 821456274..bc6a2eefb 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java +++ b/core/src/main/java/org/teavm/backend/c/generate/GenerationContext.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import org.teavm.backend.c.generators.Generator; import org.teavm.backend.c.intrinsic.Intrinsic; import org.teavm.dependency.DependencyInfo; @@ -39,10 +40,12 @@ public class GenerationContext { private List intrinsics; private List generators; private Map intrinsicCache = new HashMap<>(); + private Predicate asyncMethods; public GenerationContext(VirtualTableProvider virtualTableProvider, Characteristics characteristics, DependencyInfo dependencies, StringPool stringPool, NameProvider names, Diagnostics diagnostics, - ClassReaderSource classSource, List intrinsics, List generators) { + ClassReaderSource classSource, List intrinsics, List generators, + Predicate asyncMethods) { this.virtualTableProvider = virtualTableProvider; this.characteristics = characteristics; this.dependencies = dependencies; @@ -52,6 +55,7 @@ public class GenerationContext { this.classSource = classSource; this.intrinsics = new ArrayList<>(intrinsics); this.generators = new ArrayList<>(generators); + this.asyncMethods = asyncMethods; } public void addIntrinsic(Intrinsic intrinsic) { @@ -99,4 +103,8 @@ public class GenerationContext { } return null; } + + public boolean isAsync(MethodReference method) { + return asyncMethods.test(method); + } } diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/PlatformIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/PlatformIntrinsic.java index 5853683d9..460d35676 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/PlatformIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/PlatformIntrinsic.java @@ -30,7 +30,6 @@ public class PlatformIntrinsic implements Intrinsic { switch (method.getName()) { case "getPlatformObject": case "asJavaClass": - case "createQueue": return true; } return false; @@ -43,9 +42,6 @@ public class PlatformIntrinsic implements Intrinsic { case "asJavaClass": context.emit(invocation.getArguments().get(0)); break; - case "createQueue": - context.writer().print("NULL"); - break; } } } diff --git a/core/src/main/java/org/teavm/backend/lowlevel/transform/CoroutineTransformation.java b/core/src/main/java/org/teavm/backend/lowlevel/transform/CoroutineTransformation.java new file mode 100644 index 000000000..84255f83d --- /dev/null +++ b/core/src/main/java/org/teavm/backend/lowlevel/transform/CoroutineTransformation.java @@ -0,0 +1,502 @@ +/* + * Copyright 2018 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.lowlevel.transform; + +import com.carrotsearch.hppc.IntIntHashMap; +import com.carrotsearch.hppc.IntIntMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.teavm.common.Graph; +import org.teavm.common.GraphSplittingBackend; +import org.teavm.common.GraphUtils; +import org.teavm.model.BasicBlock; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.Instruction; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.TextLocation; +import org.teavm.model.TryCatchBlock; +import org.teavm.model.ValueType; +import org.teavm.model.Variable; +import org.teavm.model.instructions.BranchingCondition; +import org.teavm.model.instructions.BranchingInstruction; +import org.teavm.model.instructions.DoubleConstantInstruction; +import org.teavm.model.instructions.ExitInstruction; +import org.teavm.model.instructions.FloatConstantInstruction; +import org.teavm.model.instructions.InitClassInstruction; +import org.teavm.model.instructions.IntegerConstantInstruction; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.JumpInstruction; +import org.teavm.model.instructions.LongConstantInstruction; +import org.teavm.model.instructions.MonitorEnterInstruction; +import org.teavm.model.instructions.NullConstantInstruction; +import org.teavm.model.instructions.SwitchInstruction; +import org.teavm.model.instructions.SwitchTableEntry; +import org.teavm.model.util.BasicBlockMapper; +import org.teavm.model.util.BasicBlockSplitter; +import org.teavm.model.util.DefinitionExtractor; +import org.teavm.model.util.LivenessAnalyzer; +import org.teavm.model.util.PhiUpdater; +import org.teavm.model.util.ProgramUtils; +import org.teavm.model.util.TransitionExtractor; +import org.teavm.model.util.TypeInferer; +import org.teavm.model.util.UsageExtractor; +import org.teavm.model.util.VariableType; +import org.teavm.runtime.Fiber; + +public class CoroutineTransformation { + private ClassReaderSource classSource; + private LivenessAnalyzer livenessAnalysis = new LivenessAnalyzer(); + private TypeInferer variableTypes = new TypeInferer(); + private Set asyncMethods; + private Program program; + private Variable fiberVar; + private BasicBlockSplitter splitter; + private SwitchInstruction resumeSwitch; + private int parameterCount; + private ValueType returnType; + + public CoroutineTransformation(ClassReaderSource classSource, Set asyncMethods) { + this.classSource = classSource; + this.asyncMethods = asyncMethods; + } + + public void apply(Program program, MethodReference methodReference) { + if (methodReference.getClassName().equals(Fiber.class.getName())) { + return; + } + + boolean hasJob = false; + for (BasicBlock block : program.getBasicBlocks()) { + if (hasSplitInstructions(block)) { + hasJob = true; + } + } + if (!hasJob) { + return; + } + + this.program = program; + parameterCount = methodReference.parameterCount(); + returnType = methodReference.getReturnType(); + variableTypes.inferTypes(program, methodReference); + livenessAnalysis.analyze(program); + splitter = new BasicBlockSplitter(program); + int basicBlockCount = program.basicBlockCount(); + createSplitPrologue(); + for (int i = 1; i <= basicBlockCount; ++i) { + processBlock(program.basicBlockAt(i)); + } + splitter.fixProgram(); + processIrreducibleCfg(); + } + + private void createSplitPrologue() { + fiberVar = program.createVariable(); + fiberVar.setLabel("fiber"); + + BasicBlock firstBlock = program.basicBlockAt(0); + BasicBlock continueBlock = splitter.split(firstBlock, null); + BasicBlock switchStateBlock = program.createBasicBlock(); + TextLocation location = continueBlock.getFirstInstruction().getLocation(); + + InvokeInstruction getFiber = new InvokeInstruction(); + getFiber.setType(InvocationType.SPECIAL); + getFiber.setMethod(new MethodReference(Fiber.class, "current", Fiber.class)); + getFiber.setReceiver(fiberVar); + getFiber.setLocation(location); + firstBlock.add(getFiber); + + InvokeInstruction isResuming = new InvokeInstruction(); + isResuming.setType(InvocationType.SPECIAL); + isResuming.setMethod(new MethodReference(Fiber.class, "isResuming", boolean.class)); + isResuming.setInstance(fiberVar); + isResuming.setReceiver(program.createVariable()); + isResuming.setLocation(location); + firstBlock.add(isResuming); + + BranchingInstruction jumpIfResuming = new BranchingInstruction(BranchingCondition.NOT_EQUAL); + jumpIfResuming.setOperand(isResuming.getReceiver()); + jumpIfResuming.setConsequent(switchStateBlock); + jumpIfResuming.setAlternative(continueBlock); + firstBlock.add(jumpIfResuming); + + InvokeInstruction popInt = new InvokeInstruction(); + popInt.setType(InvocationType.SPECIAL); + popInt.setMethod(new MethodReference(Fiber.class, "popInt", int.class)); + popInt.setInstance(fiberVar); + popInt.setReceiver(program.createVariable()); + popInt.setLocation(location); + switchStateBlock.add(popInt); + + resumeSwitch = new SwitchInstruction(); + resumeSwitch.setDefaultTarget(continueBlock); + resumeSwitch.setCondition(popInt.getReceiver()); + resumeSwitch.setLocation(location); + switchStateBlock.add(resumeSwitch); + } + + private void processBlock(BasicBlock block) { + Map splitInstructions = collectSplitInstructions(block); + List instructionList = new ArrayList<>(splitInstructions.keySet()); + Collections.reverse(instructionList); + for (Instruction instruction : instructionList) { + BasicBlock intermediate = splitter.split(block, instruction.getPrevious()); + BasicBlock next = splitter.split(intermediate, instruction); + createSplitPoint(block, intermediate, next, splitInstructions.get(instruction)); + block = next; + } + } + + private Map collectSplitInstructions(BasicBlock block) { + if (!hasSplitInstructions(block)) { + return Collections.emptyMap(); + } + + BitSet live = new BitSet(); + TransitionExtractor transitionExtractor = new TransitionExtractor(); + block.getLastInstruction().acceptVisitor(transitionExtractor); + for (BasicBlock successor : transitionExtractor.getTargets()) { + live.or(livenessAnalysis.liveIn(successor.getIndex())); + } + for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) { + live.or(livenessAnalysis.liveIn(tryCatch.getHandler().getIndex())); + } + + Map result = new LinkedHashMap<>(); + UsageExtractor use = new UsageExtractor(); + DefinitionExtractor def = new DefinitionExtractor(); + for (Instruction instruction = block.getLastInstruction(); instruction != null; + instruction = instruction.getPrevious()) { + instruction.acceptVisitor(def); + if (def.getDefinedVariables() != null) { + for (Variable var : def.getDefinedVariables()) { + live.clear(var.getIndex()); + } + } + instruction.acceptVisitor(use); + if (use.getUsedVariables() != null) { + for (Variable var : use.getUsedVariables()) { + live.set(var.getIndex()); + } + } + + if (isSplitInstruction(instruction)) { + result.put(instruction, (BitSet) live.clone()); + } + } + return result; + } + + private boolean hasSplitInstructions(BasicBlock block) { + for (Instruction instruction : block) { + if (isSplitInstruction(instruction)) { + return true; + } + } + return false; + } + + private boolean isSplitInstruction(Instruction instruction) { + if (instruction instanceof InvokeInstruction) { + InvokeInstruction invoke = (InvokeInstruction) instruction; + MethodReference method = findRealMethod(invoke.getMethod()); + if (method.getClassName().equals(Fiber.class.getName())) { + return false; + } + return asyncMethods.contains(method); + } else if (instruction instanceof InitClassInstruction) { + return isSplittingClassInitializer(((InitClassInstruction) instruction).getClassName()); + } else { + return instruction instanceof MonitorEnterInstruction; + } + } + + private void createSplitPoint(BasicBlock block, BasicBlock intermediate, BasicBlock next, BitSet liveVars) { + int stateNumber = resumeSwitch.getEntries().size(); + Instruction splitInstruction = intermediate.getFirstInstruction(); + + JumpInstruction jumpToIntermediate = new JumpInstruction(); + jumpToIntermediate.setTarget(intermediate); + jumpToIntermediate.setLocation(splitInstruction.getLocation()); + block.add(jumpToIntermediate); + + BasicBlock restoreBlock = program.createBasicBlock(); + BasicBlock saveBlock = program.createBasicBlock(); + + SwitchTableEntry switchTableEntry = new SwitchTableEntry(); + switchTableEntry.setCondition(stateNumber); + switchTableEntry.setTarget(restoreBlock); + resumeSwitch.getEntries().add(switchTableEntry); + + InvokeInstruction isSuspending = new InvokeInstruction(); + isSuspending.setType(InvocationType.SPECIAL); + isSuspending.setMethod(new MethodReference(Fiber.class, "isSuspending", boolean.class)); + isSuspending.setInstance(fiberVar); + isSuspending.setReceiver(program.createVariable()); + isSuspending.setLocation(splitInstruction.getLocation()); + intermediate.add(isSuspending); + + BranchingInstruction branchIfSuspending = new BranchingInstruction(BranchingCondition.NOT_EQUAL); + branchIfSuspending.setOperand(isSuspending.getReceiver()); + branchIfSuspending.setConsequent(saveBlock); + branchIfSuspending.setAlternative(next); + branchIfSuspending.setLocation(splitInstruction.getLocation()); + intermediate.add(branchIfSuspending); + + restoreBlock.addAll(restoreState(liveVars)); + JumpInstruction doneRestoring = new JumpInstruction(); + doneRestoring.setTarget(intermediate); + restoreBlock.add(doneRestoring); + for (Instruction instruction : restoreBlock) { + instruction.setLocation(splitInstruction.getLocation()); + } + + for (Instruction instruction : saveState(liveVars)) { + instruction.setLocation(splitInstruction.getLocation()); + saveBlock.add(instruction); + } + for (Instruction instruction : saveStateNumber(stateNumber)) { + instruction.setLocation(splitInstruction.getLocation()); + saveBlock.add(instruction); + } + createReturnInstructions(splitInstruction.getLocation(), saveBlock); + } + + private List saveState(BitSet vars) { + List instructions = new ArrayList<>(); + for (int var = vars.nextSetBit(0); var >= 0; var = vars.nextSetBit(var + 1)) { + saveVariable(var, instructions); + } + + return instructions; + } + + private List saveStateNumber(int number) { + IntegerConstantInstruction constant = new IntegerConstantInstruction(); + constant.setReceiver(program.createVariable()); + constant.setConstant(number); + + InvokeInstruction invoke = new InvokeInstruction(); + invoke.setType(InvocationType.SPECIAL); + invoke.setMethod(new MethodReference(Fiber.class, "push", int.class, void.class)); + invoke.setInstance(fiberVar); + invoke.setArguments(constant.getReceiver()); + + return Arrays.asList(constant, invoke); + } + + private List restoreState(BitSet vars) { + List instructions = new ArrayList<>(); + int[] varArray = new int[vars.cardinality()]; + int j = 0; + for (int i = vars.nextSetBit(0); i >= 0; i = vars.nextSetBit(i + 1)) { + varArray[j++] = i; + } + + for (int i = varArray.length - 1; i >= 0; --i) { + restoreVariable(varArray[i], instructions); + } + + return instructions; + } + + private void saveVariable(int var, List instructions) { + VariableType type = variableTypes.typeOf(var); + InvokeInstruction invoke = new InvokeInstruction(); + invoke.setType(InvocationType.SPECIAL); + invoke.setInstance(fiberVar); + invoke.setArguments(program.variableAt(var)); + + switch (type) { + case INT: + invoke.setMethod(new MethodReference(Fiber.class, "push", int.class, void.class)); + break; + case LONG: + invoke.setMethod(new MethodReference(Fiber.class, "push", long.class, void.class)); + break; + case FLOAT: + invoke.setMethod(new MethodReference(Fiber.class, "push", float.class, void.class)); + break; + case DOUBLE: + invoke.setMethod(new MethodReference(Fiber.class, "push", double.class, void.class)); + break; + default: + invoke.setMethod(new MethodReference(Fiber.class, "push", Object.class, void.class)); + break; + } + + instructions.add(invoke); + } + + private void restoreVariable(int var, List instructions) { + VariableType type = variableTypes.typeOf(var); + InvokeInstruction invoke = new InvokeInstruction(); + invoke.setType(InvocationType.SPECIAL); + invoke.setInstance(fiberVar); + invoke.setReceiver(program.variableAt(var)); + + switch (type) { + case INT: + invoke.setMethod(new MethodReference(Fiber.class, "popInt", int.class)); + break; + case LONG: + invoke.setMethod(new MethodReference(Fiber.class, "popLong", long.class)); + break; + case FLOAT: + invoke.setMethod(new MethodReference(Fiber.class, "popFloat", float.class)); + break; + case DOUBLE: + invoke.setMethod(new MethodReference(Fiber.class, "popDouble", double.class)); + break; + default: + invoke.setMethod(new MethodReference(Fiber.class, "popObject", Object.class)); + break; + } + + instructions.add(invoke); + } + + private boolean isSplittingClassInitializer(String className) { + ClassReader cls = classSource.get(className); + if (cls == null) { + return false; + } + + MethodReader method = cls.getMethod(new MethodDescriptor("", ValueType.VOID)); + return method != null && asyncMethods.contains(method.getReference()); + } + + private MethodReference findRealMethod(MethodReference method) { + String clsName = method.getClassName(); + while (clsName != null) { + ClassReader cls = classSource.get(clsName); + if (cls == null) { + break; + } + MethodReader methodReader = cls.getMethod(method.getDescriptor()); + if (methodReader != null) { + return new MethodReference(clsName, method.getDescriptor()); + } + clsName = cls.getParent(); + if (clsName != null && clsName.equals(cls.getName())) { + break; + } + } + return method; + } + + private void createReturnInstructions(TextLocation location, BasicBlock block) { + ExitInstruction exit = new ExitInstruction(); + exit.setLocation(location); + if (returnType == ValueType.VOID) { + block.add(exit); + return; + } + exit.setValueToReturn(program.createVariable()); + Instruction returnValue = createReturnValueInstruction(exit.getValueToReturn()); + returnValue.setLocation(location); + block.add(returnValue); + block.add(exit); + } + + private Instruction createReturnValueInstruction(Variable target) { + if (returnType instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) returnType).getKind()) { + case BOOLEAN: + case BYTE: + case CHARACTER: + case SHORT: + case INTEGER: { + IntegerConstantInstruction instruction = new IntegerConstantInstruction(); + instruction.setReceiver(target); + return instruction; + } + case LONG: { + LongConstantInstruction instruction = new LongConstantInstruction(); + instruction.setReceiver(target); + return instruction; + } + case FLOAT: { + FloatConstantInstruction instruction = new FloatConstantInstruction(); + instruction.setReceiver(target); + return instruction; + } + case DOUBLE: { + DoubleConstantInstruction instruction = new DoubleConstantInstruction(); + instruction.setReceiver(target); + return instruction; + } + } + } + + NullConstantInstruction instruction = new NullConstantInstruction(); + instruction.setReceiver(target); + return instruction; + } + + private void processIrreducibleCfg() { + Graph graph = ProgramUtils.buildControlFlowGraph(program); + if (!GraphUtils.isIrreducible(graph)) { + return; + } + + SplittingBackend splittingBackend = new SplittingBackend(); + int[] weights = new int[graph.size()]; + for (int i = 0; i < program.basicBlockCount(); ++i) { + weights[i] = program.basicBlockAt(i).instructionCount(); + } + GraphUtils.splitIrreducibleGraph(graph, weights, splittingBackend); + new PhiUpdater().updatePhis(program, parameterCount + 1); + } + + class SplittingBackend implements GraphSplittingBackend { + @Override + public int[] split(int[] domain, int[] nodes) { + int[] copies = new int[nodes.length]; + IntIntMap map = new IntIntHashMap(); + for (int i = 0; i < nodes.length; ++i) { + int node = nodes[i]; + BasicBlock block = program.basicBlockAt(node); + BasicBlock blockCopy = program.createBasicBlock(); + ProgramUtils.copyBasicBlock(block, blockCopy); + copies[i] = blockCopy.getIndex(); + map.put(nodes[i], copies[i] + 1); + } + BasicBlockMapper copyBlockMapper = new BasicBlockMapper((int block) -> { + int mappedIndex = map.get(block); + return mappedIndex == 0 ? block : mappedIndex - 1; + }); + for (int copy : copies) { + copyBlockMapper.transform(program.basicBlockAt(copy)); + } + for (int domainNode : domain) { + copyBlockMapper.transform(program.basicBlockAt(domainNode)); + } + return copies; + } + } +} diff --git a/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java index 699ca5d5b..540ed1f7e 100644 --- a/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java +++ b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java @@ -51,6 +51,8 @@ import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.JumpInstruction; import org.teavm.model.instructions.LongConstantInstruction; +import org.teavm.model.instructions.MonitorEnterInstruction; +import org.teavm.model.instructions.MonitorExitInstruction; import org.teavm.model.instructions.NullConstantInstruction; import org.teavm.model.instructions.RaiseInstruction; import org.teavm.model.instructions.SwitchInstruction; @@ -258,7 +260,8 @@ public class ExceptionHandlingShadowStackContributor { private boolean isCallInstruction(Instruction insn) { if (insn instanceof InitClassInstruction || insn instanceof ConstructInstruction || insn instanceof ConstructArrayInstruction || insn instanceof ConstructMultiArrayInstruction - || insn instanceof CloneArrayInstruction || insn instanceof RaiseInstruction) { + || insn instanceof CloneArrayInstruction || insn instanceof RaiseInstruction + || insn instanceof MonitorEnterInstruction || insn instanceof MonitorExitInstruction) { return true; } else if (insn instanceof InvokeInstruction) { MethodReference method = ((InvokeInstruction) insn).getMethod(); diff --git a/core/src/main/java/org/teavm/model/lowlevel/GCShadowStackContributor.java b/core/src/main/java/org/teavm/model/lowlevel/GCShadowStackContributor.java index 85733d79e..f6aa5e2d6 100644 --- a/core/src/main/java/org/teavm/model/lowlevel/GCShadowStackContributor.java +++ b/core/src/main/java/org/teavm/model/lowlevel/GCShadowStackContributor.java @@ -50,6 +50,8 @@ import org.teavm.model.instructions.InitClassInstruction; import org.teavm.model.instructions.IntegerConstantInstruction; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.MonitorEnterInstruction; +import org.teavm.model.instructions.MonitorExitInstruction; import org.teavm.model.instructions.RaiseInstruction; import org.teavm.model.util.DefinitionExtractor; import org.teavm.model.util.GraphColorer; @@ -155,7 +157,8 @@ public class GCShadowStackContributor { if (insn instanceof InvokeInstruction || insn instanceof InitClassInstruction || insn instanceof ConstructInstruction || insn instanceof ConstructArrayInstruction || insn instanceof ConstructMultiArrayInstruction - || insn instanceof CloneArrayInstruction || insn instanceof RaiseInstruction) { + || insn instanceof CloneArrayInstruction || insn instanceof RaiseInstruction + || insn instanceof MonitorEnterInstruction || insn instanceof MonitorExitInstruction) { if (insn instanceof InvokeInstruction && !characteristics.isManaged(((InvokeInstruction) insn).getMethod())) { continue; diff --git a/core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java b/core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java index fe15aa7d3..03360bdd1 100644 --- a/core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java +++ b/core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java @@ -40,6 +40,7 @@ import org.teavm.model.MethodReference; import org.teavm.model.ProgramReader; import org.teavm.model.VariableReader; import org.teavm.model.instructions.AbstractInstructionReader; +import org.teavm.runtime.Fiber; public class AsyncMethodFinder { private Set asyncMethods = new HashSet<>(); @@ -142,6 +143,10 @@ public class AsyncMethodFinder { } private void add(MethodReference methodRef, CallStack stack) { + if (methodRef.getClassName().equals(Fiber.class.getName())) { + return; + } + if (!asyncMethods.add(methodRef)) { return; } diff --git a/core/src/main/java/org/teavm/model/util/BasicBlockSplitter.java b/core/src/main/java/org/teavm/model/util/BasicBlockSplitter.java index 829a39c54..7fc8bbe3c 100644 --- a/core/src/main/java/org/teavm/model/util/BasicBlockSplitter.java +++ b/core/src/main/java/org/teavm/model/util/BasicBlockSplitter.java @@ -67,7 +67,7 @@ public class BasicBlockSplitter { public BasicBlock split(BasicBlock block, Instruction afterInstruction) { initIfNecessary(); - if (afterInstruction.getBasicBlock() != block) { + if (afterInstruction != null && afterInstruction.getBasicBlock() != block) { throw new IllegalArgumentException(); } @@ -88,10 +88,18 @@ public class BasicBlockSplitter { isLastInSequence.add((byte) 1); splitBlock.getTryCatchBlocks().addAll(ProgramUtils.copyTryCatches(block, program)); - while (afterInstruction.getNext() != null) { - Instruction nextInstruction = afterInstruction.getNext(); - nextInstruction.delete(); - splitBlock.add(nextInstruction); + if (afterInstruction != null) { + while (afterInstruction.getNext() != null) { + Instruction nextInstruction = afterInstruction.getNext(); + nextInstruction.delete(); + splitBlock.add(nextInstruction); + } + } else { + while (block.getFirstInstruction() != null) { + Instruction instruction = block.getFirstInstruction(); + instruction.delete(); + splitBlock.add(instruction); + } } return splitBlock; diff --git a/core/src/main/java/org/teavm/runtime/EventQueue.java b/core/src/main/java/org/teavm/runtime/EventQueue.java new file mode 100644 index 000000000..f8bbb6ff6 --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/EventQueue.java @@ -0,0 +1,147 @@ +/* + * Copyright 2018 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.runtime; + +import java.util.Arrays; +import org.teavm.interop.Import; +import org.teavm.interop.StaticInit; + +@StaticInit +public final class EventQueue { + private static Node[] data = new Node[16]; + private static int size; + private static boolean finished; + private static int idGenerator; + + private EventQueue() { + } + + public static int offer(Event action) { + return offer(action, System.currentTimeMillis()); + } + + public static int offer(Event action, long time) { + ensureCapacity(size + 1); + int current = size; + while (current > 0) { + int parent = (current - 1) / 2; + if (time < data[parent].time) { + data[current] = data[parent]; + current = parent; + } else { + break; + } + } + int id = idGenerator++; + data[current] = new Node(id, action, time); + ++size; + if (current == 0) { + interrupt(); + } + return id; + } + + public static void kill(int id) { + for (int i = 0; i < data.length; ++i) { + if (data[i].id == id) { + remove(i); + break; + } + } + } + + public static void process() { + while (!finished) { + next(); + } + } + + static void stop() { + finished = true; + } + + private static void next() { + while (data.length == 0) { + waitUntil(System.currentTimeMillis() + 1000); + } + Node node = data[0]; + waitUntil(node.time); + if (node.time <= System.currentTimeMillis()) { + remove(0); + node.event.run(); + } + } + + private static void remove(int index) { + Node item = data[size - 1]; + while (true) { + int left = index * 2 + 1; + int right = left + 1; + int next; + if (left >= size) { + break; + } else if (right >= size || data[left].time < data[right].time) { + next = left; + } else { + next = right; + } + if (item.time <= data[next].time) { + break; + } + data[index] = data[next]; + index = next; + } + data[index] = item; + } + + private static void waitUntil(long time) { + long diff = time - System.currentTimeMillis(); + if (diff <= 0) { + return; + } + waitFor(diff); + } + + @Import(name = "teavm_waitFor") + private static native void waitFor(long time); + + @Import(name = "teavm_interrupt") + private static native void interrupt(); + + private static void ensureCapacity(int capacity) { + if (data.length >= capacity) { + return; + } + capacity = Math.max(capacity, data.length * 3 / 2); + data = Arrays.copyOf(data, capacity); + } + + static class Node { + final int id; + final Event event; + final long time; + + Node(int id, Event event, long time) { + this.id = id; + this.event = event; + this.time = time; + } + } + + public interface Event { + void run(); + } +} diff --git a/core/src/main/java/org/teavm/runtime/Fiber.java b/core/src/main/java/org/teavm/runtime/Fiber.java new file mode 100644 index 000000000..fabaa2b3d --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/Fiber.java @@ -0,0 +1,243 @@ +/* + * Copyright 2018 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.runtime; + +import java.util.Arrays; +import org.teavm.interop.AsyncCallback; +import org.teavm.interop.StaticInit; +import org.teavm.interop.Unmanaged; + +@StaticInit +public class Fiber { + public static final int STATE_RUNNING = 0; + public static final int STATE_SUSPENDING = 1; + public static final int STATE_RESUMING = 2; + + private int[] intValues; + private int intTop; + private long[] longValues; + private int longTop; + private float[] floatValues; + private int floatTop; + private double[] doubleValues; + private int doubleTop; + private Object[] objectValues; + private int objectTop; + private int state; + private FiberRunner runner; + private Object result; + private Throwable exception; + + private static Fiber current; + + private Fiber(FiberRunner runner) { + this.runner = runner; + } + + public void push(int value) { + if (intValues == null) { + intValues = new int[4]; + } else if (intTop + 1 == intValues.length) { + intValues = Arrays.copyOf(intValues, intValues.length * 3 / 2); + } + intValues[intTop++] = value; + } + + public void push(long value) { + if (longValues == null) { + longValues = new long[4]; + } else if (longTop + 1 == longValues.length) { + longValues = Arrays.copyOf(longValues, longValues.length * 3 / 2); + } + longValues[longTop++] = value; + } + + public void push(float value) { + if (floatValues == null) { + floatValues = new float[4]; + } else if (floatTop + 1 == floatValues.length) { + floatValues = Arrays.copyOf(floatValues, floatValues.length * 3 / 2); + } + floatValues[floatTop++] = value; + } + + public void push(double value) { + if (doubleValues == null) { + doubleValues = new double[4]; + } else if (doubleTop + 4 == doubleValues.length) { + doubleValues = Arrays.copyOf(doubleValues, doubleValues.length * 3 / 2); + } + doubleValues[doubleTop++] = value; + } + + public void push(Object value) { + if (objectValues == null) { + objectValues = new Object[4]; + } else if (objectTop + 4 == objectValues.length) { + objectValues = Arrays.copyOf(objectValues, objectValues.length * 3 / 2); + } + objectValues[objectTop++] = value; + } + + @Unmanaged + public int popInt() { + return intValues[--intTop]; + } + + @Unmanaged + public long popLong() { + return longValues[--longTop]; + } + + @Unmanaged + public float popFloat() { + return floatValues[--floatTop]; + } + + @Unmanaged + public double popDouble() { + return doubleValues[--doubleTop]; + } + + @Unmanaged + public Object popObject() { + Object result = objectValues[--objectTop]; + objectValues[objectTop] = null; + return result; + } + + @Unmanaged + public static Fiber current() { + return current; + } + + @Unmanaged + public boolean isSuspending() { + return state == STATE_SUSPENDING; + } + + @Unmanaged + public boolean isResuming() { + return state == STATE_RESUMING; + } + + @Unmanaged + public static boolean getBoolean(Object v) { + return v != null ? (Boolean) v : false; + } + + @Unmanaged + public static byte getByte(Object v) { + return v != null ? (Byte) v : 0; + } + + @Unmanaged + public static short getShort(Object v) { + return v != null ? (Short) v : 0; + } + + @Unmanaged + public static int getInt(Object v) { + return v != null ? (Integer) v : 0; + } + + @Unmanaged + public static char getChar(Object v) { + return v != null ? (Character) v : 0; + } + + @Unmanaged + public static long getLong(Object v) { + return v != null ? (Long) v : 0; + } + + @Unmanaged + public static float getFloat(Object v) { + return v != null ? (Float) v : 0; + } + + @Unmanaged + public static double getDouble(Object v) { + return v != null ? (Double) v : 0; + } + + public static Object suspend(AsyncCall call) throws Throwable { + Fiber fiber = current(); + Thread javaThread = Thread.currentThread(); + if (fiber.isResuming()) { + fiber.state = STATE_RUNNING; + if (fiber.exception != null) { + throw fiber.exception; + } + return fiber.result; + } + + fiber.state = STATE_SUSPENDING; + call.run(new AsyncCallback() { + @Override + public void complete(Object result) { + setCurrentThread(javaThread); + fiber.result = result; + fiber.resume(); + } + + @Override + public void error(Throwable e) { + setCurrentThread(javaThread); + fiber.exception = e; + fiber.resume(); + } + }); + return null; + } + + static native void setCurrentThread(Thread thread); + + public static void start(FiberRunner runner) { + new Fiber(runner).start(); + } + + static void startMain() { + start(() -> { + runMain(); + if (!current().isSuspending()) { + EventQueue.stop(); + } + }); + } + + static native void runMain(); + + private void start() { + Fiber former = current; + current = this; + runner.run(); + current = former; + } + + private void resume() { + state = STATE_RESUMING; + start(); + } + + public interface FiberRunner { + void run(); + } + + public interface AsyncCall { + void run(AsyncCallback callback); + } +} diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index 0c453938c..9bbb35648 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; @@ -377,6 +378,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return; } + target.analyzeBeforeOptimizations(new ListableClassReaderSourceAdapter( + dependencyAnalyzer.getClassSource(), + new LinkedHashSet<>(dependencyAnalyzer.getReachableClasses()))); + boolean isLazy = optimizationLevel == TeaVMOptimizationLevel.SIMPLE; ListableClassHolderSource classSet; if (isLazy) { @@ -869,4 +874,24 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return classNames; } } + + static class ListableClassReaderSourceAdapter implements ListableClassReaderSource { + private ClassReaderSource classSource; + private Set classes; + + ListableClassReaderSourceAdapter(ClassReaderSource classSource, Set classes) { + this.classSource = classSource; + this.classes = Collections.unmodifiableSet(classes); + } + + @Override + public Set getClassNames() { + return classes; + } + + @Override + public ClassReader get(String name) { + return classes.contains(name) ? classSource.get(name) : null; + } + } } diff --git a/core/src/main/java/org/teavm/vm/TeaVMTarget.java b/core/src/main/java/org/teavm/vm/TeaVMTarget.java index d0d17f22a..58e81e303 100644 --- a/core/src/main/java/org/teavm/vm/TeaVMTarget.java +++ b/core/src/main/java/org/teavm/vm/TeaVMTarget.java @@ -21,6 +21,7 @@ import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyListener; import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ListableClassHolderSource; +import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodReader; import org.teavm.model.Program; import org.teavm.vm.spi.TeaVMHostExtension; @@ -38,6 +39,9 @@ public interface TeaVMTarget { void contributeDependencies(DependencyAnalyzer dependencyAnalyzer); + default void analyzeBeforeOptimizations(ListableClassReaderSource classSource) { + } + void beforeOptimizations(Program program, MethodReader method); void afterOptimizations(Program program, MethodReader method); diff --git a/core/src/main/resources/org/teavm/backend/c/runtime.c b/core/src/main/resources/org/teavm/backend/c/runtime.c index d4f7c8b43..c27ac941a 100644 --- a/core/src/main/resources/org/teavm/backend/c/runtime.c +++ b/core/src/main/resources/org/teavm/backend/c/runtime.c @@ -13,6 +13,7 @@ #include #include #include +#include #endif #ifdef _MSC_VER @@ -29,6 +30,8 @@ typedef struct JavaArray JavaArray; typedef struct JavaClass JavaClass; typedef struct JavaString JavaString; +static void* gc_heapAddress = NULL; + #define PACK_CLASS(cls) ((int32_t) ((uintptr_t) ((char*) (cls) - TeaVM_beforeClasses) >> 3)) #define UNPACK_CLASS(cls) ((JavaClass*) (TeaVM_beforeClasses + ((cls) << 3))) #define CLASS_OF(obj) (UNPACK_CLASS(((JavaObject*) (obj))->header)) @@ -42,6 +45,11 @@ typedef struct JavaString JavaString; #define TO_SHORT(i) ((((i) << 16) >> 16)) #define TO_CHAR(i) ((char16_t) (i)) +#define PACK_MONITOR(ref) (((int32_t) ((uintptr_t) (ref) - (uintptr_t) gc_heapAddress) / sizeof(int)) | 0x80000000) +#define UNPACK_MONITOR(ref) ((ref & 0x80000000) != 0 \ + ? (void*) (((uintptr_t) ((ref & 0x7FFFFFFF) * sizeof(int)) + (uintptr_t) gc_heapAddress)) \ + : NULL) + static inline int32_t compare_i32(int32_t a, int32_t b) { return a > b ? INT32_C(1) : a < b ? INT32_C(-1) : INT32_C(0); } @@ -100,7 +108,6 @@ static void** stackTop; static void* gc_gcStorageAddress = NULL; static int32_t gc_gcStorageSize = INT32_C(0); -static void* gc_heapAddress = NULL; static void* gc_regionsAddress = NULL; static int32_t gc_regionSize = INT32_C(32768); static int32_t gc_regionMaxCount = INT32_C(0); @@ -156,8 +163,31 @@ static inline void* teavm_lookupResourceValue(TeaVM_ResourceMap *map, JavaString static JavaArray* teavm_resourceMapKeys(TeaVM_ResourceMap *); + +#ifdef __GNUC__ +static timer_t teavm_queueTimer; +#endif + static void TeaVM_beforeInit() { srand(time(NULL)); + + #ifdef __GNUC__ + struct sigaction sigact; + sigact.sa_flags = 0; + sigact.sa_handler = NULL; + sigaction(SIGRTMIN, &sigact, NULL); + + sigset_t signals; + sigemptyset(&signals ); + sigaddset(&signals, SIGRTMIN); + sigprocmask(SIG_BLOCK, &signals, NULL); + + struct sigevent sev; + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = SIGRTMIN; + timer_create(CLOCK_REALTIME, &sev, &teavm_queueTimer); + + #endif } #ifdef __GNUC__ @@ -294,4 +324,27 @@ static inline float teavm_reinterpretIntToFloat(int32_t v) { union { int32_t intValue; float floatValue; } conv; conv.intValue = v; return conv.floatValue; -} \ No newline at end of file +} + +#ifdef __GNUC__ + +static void teavm_waitFor(int64_t timeout) { + struct itimerspec its = {0}; + its.it_value.tv_sec = timeout / 1000; + its.it_value.tv_nsec = (timeout % 1000) * 1000000L; + timer_settime(teavm_queueTimer, 0, &its, NULL); + + sigset_t signals; + sigemptyset(&signals); + sigaddset(&signals, SIGRTMIN); + siginfo_t actualSignal; + sigwaitinfo(&signals, &actualSignal); +} + +static void teavm_interrupt() { + struct itimerspec its = {0}; + timer_settime(teavm_queueTimer, 0, &its, NULL); + raise(SIGRTMIN); +} + +#endif \ No newline at end of file diff --git a/platform/src/main/java/org/teavm/platform/async/AsyncCallback.java b/interop/core/src/main/java/org/teavm/interop/AsyncCallback.java similarity index 84% rename from platform/src/main/java/org/teavm/platform/async/AsyncCallback.java rename to interop/core/src/main/java/org/teavm/interop/AsyncCallback.java index 4f6a63eac..c6c1fa4f7 100644 --- a/platform/src/main/java/org/teavm/platform/async/AsyncCallback.java +++ b/interop/core/src/main/java/org/teavm/interop/AsyncCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Alexey Andreev. + * Copyright 2018 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.platform.async; +package org.teavm.interop; -/** - * - * @author Alexey Andreev - * @param - */ public interface AsyncCallback { void complete(T result); diff --git a/platform/src/main/java/org/teavm/platform/LowLevelQueue.java b/platform/src/main/java/org/teavm/platform/LowLevelQueue.java new file mode 100644 index 000000000..1edcbf674 --- /dev/null +++ b/platform/src/main/java/org/teavm/platform/LowLevelQueue.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019 konsoletyper. + * + * 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.platform; + +import java.util.ArrayDeque; +import java.util.Queue; + +public class LowLevelQueue extends PlatformQueue { + Queue q = new ArrayDeque<>(); + + @Override + public int getLength() { + return q.size(); + } + + @Override + void push(PlatformObject obj) { + } + + @Override + PlatformObject shift() { + return null; + } + + @Override + public void add(T e) { + q.add(e); + } + + @Override + public T remove() { + return q.remove(); + } +} diff --git a/platform/src/main/java/org/teavm/platform/Platform.java b/platform/src/main/java/org/teavm/platform/Platform.java index 9e395a59f..74f2b5a53 100644 --- a/platform/src/main/java/org/teavm/platform/Platform.java +++ b/platform/src/main/java/org/teavm/platform/Platform.java @@ -21,6 +21,8 @@ import org.teavm.backend.javascript.spi.InjectedBy; import org.teavm.dependency.PluggableDependency; import org.teavm.interop.Address; import org.teavm.interop.DelegateTo; +import org.teavm.interop.PlatformMarker; +import org.teavm.interop.PlatformMarkers; import org.teavm.interop.Unmanaged; import org.teavm.jso.JSBody; import org.teavm.jso.JSObject; @@ -193,7 +195,16 @@ public final class Platform { } @JSBody(script = "return [];") - public static native PlatformQueue createQueue(); + public static PlatformQueue createQueue() { + if (isLowLevel()) { + return new LowLevelQueue<>(); + } else { + return createQueueJs(); + } + } + + @JSBody(script = "return [];") + private static native PlatformQueue createQueueJs(); public static PlatformString stringFromCharCode(int charCode) { return JSString.fromCharCode(charCode).cast(); @@ -230,4 +241,9 @@ public final class Platform { public static String getName(PlatformClass cls) { return cls.getMetadata().getName(); } + + @PlatformMarker(PlatformMarkers.LOW_LEVEL) + private static boolean isLowLevel() { + return false; + } } diff --git a/platform/src/main/java/org/teavm/platform/PlatformQueue.java b/platform/src/main/java/org/teavm/platform/PlatformQueue.java index eae38420c..fb7b853a7 100644 --- a/platform/src/main/java/org/teavm/platform/PlatformQueue.java +++ b/platform/src/main/java/org/teavm/platform/PlatformQueue.java @@ -33,11 +33,11 @@ public abstract class PlatformQueue implements JSObject { abstract PlatformObject shift(); - public final void add(T e) { + public void add(T e) { push(wrap(e)); } - public final T remove() { + public T remove() { return unwrap(shift()); } diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncCallClass.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncCallClass.java new file mode 100644 index 000000000..12176889d --- /dev/null +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncCallClass.java @@ -0,0 +1,20 @@ +/* + * Copyright 2018 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.platform.plugin; + +@interface AsyncCallClass { + String value(); +} diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncCallbackWrapper.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncCallbackWrapper.java index 8d82686f9..b12b70ca8 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/AsyncCallbackWrapper.java +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncCallbackWrapper.java @@ -15,7 +15,7 @@ */ package org.teavm.platform.plugin; -import org.teavm.platform.async.AsyncCallback; +import org.teavm.interop.AsyncCallback; class AsyncCallbackWrapper implements AsyncCallback { private AsyncCallback realAsyncCallback; diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncLowLevelDependencyListener.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncLowLevelDependencyListener.java new file mode 100644 index 000000000..1df26e0af --- /dev/null +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncLowLevelDependencyListener.java @@ -0,0 +1,230 @@ +/* + * Copyright 2018 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.platform.plugin; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.teavm.dependency.AbstractDependencyListener; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.MethodDependency; +import org.teavm.interop.Async; +import org.teavm.interop.AsyncCallback; +import org.teavm.model.AnnotationReader; +import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHolder; +import org.teavm.model.ElementModifier; +import org.teavm.model.FieldHolder; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.ValueType; +import org.teavm.model.Variable; +import org.teavm.model.instructions.ExitInstruction; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.PutFieldInstruction; +import org.teavm.runtime.Fiber; + +public class AsyncLowLevelDependencyListener extends AbstractDependencyListener { + private Set generatedClassNames = new HashSet<>(); + + @Override + public void methodReached(DependencyAgent agent, MethodDependency method) { + if (method.getMethod() != null && method.getMethod().getAnnotations().get(Async.class.getName()) != null) { + ClassHolder cls = generateCall(method.getMethod()); + if (cls != null) { + agent.submitClass(cls); + } + } + } + + private ClassHolder generateCall(MethodReader method) { + ClassHolder cls = generateClassDecl(method); + if (cls == null) { + return null; + } + cls.addMethod(generateConstructor(method, cls.getName())); + cls.addMethod(generateRun(method, cls.getName())); + return cls; + } + + private ClassHolder generateClassDecl(MethodReader method) { + AnnotationReader annot = method.getAnnotations().get(AsyncCallClass.class.getName()); + String className = annot.getValue("value").getString(); + if (!generatedClassNames.add(className)) { + return null; + } + ClassHolder cls = new ClassHolder(className); + + cls.getInterfaces().add(Fiber.class.getName() + "$AsyncCall"); + + List types = new ArrayList<>(); + if (!method.hasModifier(ElementModifier.STATIC)) { + types.add(ValueType.object(method.getOwnerName())); + FieldHolder field = new FieldHolder("instance"); + field.setType(ValueType.object(method.getOwnerName())); + cls.addField(field); + } + ValueType[] parameterTypes = method.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; ++i) { + types.add(parameterTypes[i]); + FieldHolder field = new FieldHolder("param" + i); + field.setType(parameterTypes[i]); + cls.addField(field); + } + types.add(ValueType.VOID); + MethodHolder constructor = new MethodHolder("", types.toArray(new ValueType[0])); + cls.addMethod(constructor); + return cls; + } + + private MethodHolder generateConstructor(MethodReader method, String className) { + List types = new ArrayList<>(); + if (!method.hasModifier(ElementModifier.STATIC)) { + types.add(ValueType.object(method.getOwnerName())); + } + Collections.addAll(types, method.getParameterTypes()); + types.add(ValueType.VOID); + MethodHolder constructor = new MethodHolder("", types.toArray(new ValueType[0])); + + Program program = new Program(); + constructor.setProgram(program); + BasicBlock block = program.createBasicBlock(); + Variable instance = program.createVariable(); + + if (!method.hasModifier(ElementModifier.STATIC)) { + PutFieldInstruction putField = new PutFieldInstruction(); + putField.setValue(program.createVariable()); + putField.setField(new FieldReference(className, "instance")); + putField.setFieldType(ValueType.object(method.getOwnerName())); + putField.setInstance(instance); + block.add(putField); + } + + for (int i = 0; i < method.parameterCount(); ++i) { + PutFieldInstruction putField = new PutFieldInstruction(); + putField.setValue(program.createVariable()); + putField.setField(new FieldReference(className, "param" + i)); + putField.setFieldType(method.parameterType(i)); + putField.setInstance(instance); + block.add(putField); + } + + block.add(new ExitInstruction()); + return constructor; + } + + private MethodHolder generateRun(MethodReader method, String className) { + MethodHolder runMethod = new MethodHolder("run", ValueType.parse(AsyncCallback.class), ValueType.VOID); + Program program = new Program(); + runMethod.setProgram(program); + BasicBlock block = program.createBasicBlock(); + Variable instance = program.createVariable(); + Variable callback = program.createVariable(); + + InvokeInstruction call = new InvokeInstruction(); + call.setType(InvocationType.SPECIAL); + List types = new ArrayList<>(); + ValueType[] parameterTypes = method.getParameterTypes(); + List arguments = new ArrayList<>(call.getArguments()); + + if (!method.hasModifier(ElementModifier.STATIC)) { + GetFieldInstruction getField = new GetFieldInstruction(); + getField.setReceiver(program.createVariable()); + getField.setInstance(instance); + getField.setField(new FieldReference(className, "instance")); + getField.setFieldType(ValueType.object(method.getOwnerName())); + block.add(getField); + call.setInstance(getField.getReceiver()); + } + for (int i = 0; i < parameterTypes.length; ++i) { + GetFieldInstruction getField = new GetFieldInstruction(); + getField.setReceiver(program.createVariable()); + getField.setInstance(instance); + getField.setField(new FieldReference(className, "param" + i)); + getField.setFieldType(parameterTypes[i]); + block.add(getField); + arguments.add(getField.getReceiver()); + types.add(parameterTypes[i]); + } + + types.add(ValueType.parse(AsyncCallback.class)); + arguments.add(callback); + + types.add(ValueType.VOID); + call.setMethod(new MethodReference(method.getOwnerName(), method.getName(), types.toArray(new ValueType[0]))); + call.setArguments(arguments.toArray(new Variable[0])); + block.add(call); + + if (method.getResultType() == ValueType.VOID) { + block.add(new ExitInstruction()); + } else { + Variable result = program.createVariable(); + call.setReceiver(result); + ExitInstruction exit = new ExitInstruction(); + exit.setValueToReturn(castToObject(block, result, method.getResultType())); + block.add(exit); + } + + return runMethod; + } + + private Variable castToObject(BasicBlock block, Variable value, ValueType type) { + if (type instanceof ValueType.Primitive) { + InvokeInstruction invoke = new InvokeInstruction(); + invoke.setType(InvocationType.SPECIAL); + invoke.setArguments(value); + invoke.setReceiver(block.getProgram().createVariable()); + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + invoke.setMethod(new MethodReference(Boolean.class, "valueOf", boolean.class, Boolean.class)); + break; + case BYTE: + invoke.setMethod(new MethodReference(Byte.class, "valueOf", byte.class, Byte.class)); + break; + case SHORT: + invoke.setMethod(new MethodReference(Short.class, "valueOf", short.class, Short.class)); + break; + case CHARACTER: + invoke.setMethod(new MethodReference(Character.class, "valueOf", char.class, Character.class)); + break; + case INTEGER: + invoke.setMethod(new MethodReference(Integer.class, "valueOf", int.class, Integer.class)); + break; + case LONG: + invoke.setMethod(new MethodReference(Long.class, "valueOf", long.class, Long.class)); + break; + case FLOAT: + invoke.setMethod(new MethodReference(Float.class, "valueOf", float.class, Float.class)); + break; + case DOUBLE: + invoke.setMethod(new MethodReference(Double.class, "valueOf", double.class, Double.class)); + break; + } + + block.add(invoke); + return invoke.getReceiver(); + } + + return value; + } +} diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java index 27022390a..ef74bbed2 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodGenerator.java @@ -24,13 +24,13 @@ import org.teavm.backend.javascript.spi.VirtualMethodContributorContext; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; +import org.teavm.interop.AsyncCallback; import org.teavm.model.ClassReader; import org.teavm.model.ElementModifier; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; -import org.teavm.platform.async.AsyncCallback; public class AsyncMethodGenerator implements Generator, DependencyPlugin, VirtualMethodContributor { private static final MethodDescriptor completeMethod = new MethodDescriptor("complete", Object.class, void.class); diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java index 31d65b88d..fbf906e28 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java @@ -15,8 +15,14 @@ */ package org.teavm.platform.plugin; +import java.util.ArrayList; +import java.util.List; import org.teavm.backend.javascript.spi.GeneratedBy; import org.teavm.interop.Async; +import org.teavm.interop.AsyncCallback; +import org.teavm.model.AnnotationHolder; +import org.teavm.model.AnnotationValue; +import org.teavm.model.BasicBlock; import org.teavm.model.CallLocation; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; @@ -24,12 +30,27 @@ import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ElementModifier; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; import org.teavm.model.ValueType; -import org.teavm.platform.async.AsyncCallback; +import org.teavm.model.Variable; +import org.teavm.model.instructions.CastInstruction; +import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.ExitInstruction; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.runtime.Fiber; public class AsyncMethodProcessor implements ClassHolderTransformer { + private boolean lowLevel; + + public AsyncMethodProcessor(boolean lowLevel) { + this.lowLevel = lowLevel; + } + @Override public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { + int suffix = 0; for (MethodHolder method : cls.getMethods()) { if (method.hasModifier(ElementModifier.NATIVE) && method.getAnnotations().get(Async.class.getName()) != null @@ -50,7 +71,112 @@ public class AsyncMethodProcessor implements ClassHolderTransformer { method.getReference(), asyncMethod.getReference()); } } + + if (lowLevel) { + generateLowLevelCall(method, suffix++); + } } } } + + private void generateLowLevelCall(MethodHolder method, int suffix) { + String className = method.getOwnerName() + "$" + method.getName() + "$" + suffix; + AnnotationHolder classNameAnnot = new AnnotationHolder(AsyncCallClass.class.getName()); + classNameAnnot.getValues().put("value", new AnnotationValue(className)); + method.getAnnotations().add(classNameAnnot); + + method.getModifiers().remove(ElementModifier.NATIVE); + + Program program = new Program(); + method.setProgram(program); + BasicBlock block = program.createBasicBlock(); + + InvokeInstruction constructorInvocation = new InvokeInstruction(); + constructorInvocation.setType(InvocationType.SPECIAL); + List signature = new ArrayList<>(); + + Variable instanceVar = program.createVariable(); + List arguments = new ArrayList<>(constructorInvocation.getArguments()); + if (!method.hasModifier(ElementModifier.STATIC)) { + arguments.add(instanceVar); + signature.add(ValueType.object(method.getOwnerName())); + } + for (int i = 0; i < method.parameterCount(); ++i) { + arguments.add(program.createVariable()); + signature.add(method.parameterType(i)); + } + signature.add(ValueType.VOID); + + ConstructInstruction newInstruction = new ConstructInstruction(); + newInstruction.setReceiver(program.createVariable()); + newInstruction.setType(className); + block.add(newInstruction); + + constructorInvocation.setInstance(newInstruction.getReceiver()); + constructorInvocation.setMethod(new MethodReference(className, "", signature.toArray(new ValueType[0]))); + constructorInvocation.setArguments(arguments.toArray(new Variable[0])); + block.add(constructorInvocation); + + InvokeInstruction suspendInvocation = new InvokeInstruction(); + suspendInvocation.setType(InvocationType.SPECIAL); + suspendInvocation.setMethod(new MethodReference(Fiber.class, "suspend", Fiber.AsyncCall.class, Object.class)); + suspendInvocation.setArguments(newInstruction.getReceiver()); + suspendInvocation.setReceiver(program.createVariable()); + block.add(suspendInvocation); + + Variable result = suspendInvocation.getReceiver(); + ExitInstruction exitInstruction = new ExitInstruction(); + ValueType returnType = method.getResultType(); + if (returnType instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) returnType).getKind()) { + case BOOLEAN: + result = castPrimitive(block, result, "Boolean", returnType); + break; + case BYTE: + result = castPrimitive(block, result, "Byte", returnType); + break; + case SHORT: + result = castPrimitive(block, result, "Short", returnType); + break; + case CHARACTER: + result = castPrimitive(block, result, "Char", returnType); + break; + case INTEGER: + result = castPrimitive(block, result, "Int", returnType); + break; + case FLOAT: + result = castPrimitive(block, result, "Float", returnType); + break; + case LONG: + result = castPrimitive(block, result, "Long", returnType); + break; + case DOUBLE: + result = castPrimitive(block, result, "Double", returnType); + break; + } + } else if (returnType == ValueType.VOID) { + result = null; + } else { + CastInstruction cast = new CastInstruction(); + cast.setValue(result); + cast.setTargetType(returnType); + cast.setReceiver(program.createVariable()); + block.add(cast); + result = cast.getReceiver(); + } + + exitInstruction.setValueToReturn(result); + block.add(exitInstruction); + } + + private Variable castPrimitive(BasicBlock block, Variable value, String name, ValueType type) { + InvokeInstruction invoke = new InvokeInstruction(); + invoke.setType(InvocationType.SPECIAL); + invoke.setMethod(new MethodReference(Fiber.class.getName(), "get" + name, + ValueType.object("java.lang.Object"), type)); + invoke.setArguments(value); + invoke.setReceiver(block.getProgram().createVariable()); + block.add(invoke); + return invoke.getReceiver(); + } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/PlatformPlugin.java b/platform/src/main/java/org/teavm/platform/plugin/PlatformPlugin.java index b79fc1d65..4b49802b2 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/PlatformPlugin.java +++ b/platform/src/main/java/org/teavm/platform/plugin/PlatformPlugin.java @@ -56,6 +56,7 @@ public class PlatformPlugin implements TeaVMPlugin { return method.getAnnotations().get(Async.class.getName()) != null ? new AsyncMethodGenerator() : null; }); + host.add(new AsyncDependencyListener()); jsHost.addVirtualMethods(new AsyncMethodGenerator()); } else if (!isBootstrap()) { host.add(new StringAmplifierTransformer()); @@ -101,12 +102,15 @@ public class PlatformPlugin implements TeaVMPlugin { } } - host.add(new AsyncMethodProcessor()); + host.add(new AsyncMethodProcessor(host.getExtension(TeaVMJavaScriptHost.class) == null)); host.add(new NewInstanceDependencySupport()); host.add(new ClassLookupDependencySupport()); host.add(new EnumDependencySupport()); host.add(new PlatformDependencyListener()); - host.add(new AsyncDependencyListener()); + + if (host.getExtension(TeaVMJavaScriptHost.class) == null) { + host.add(new AsyncLowLevelDependencyListener()); + } TeaVMPluginUtil.handleNatives(host, Platform.class); TeaVMPluginUtil.handleNatives(host, PlatformQueue.class); diff --git a/tests/src/test/java/org/teavm/vm/VMTest.java b/tests/src/test/java/org/teavm/vm/VMTest.java index b1a6c8ee3..4dfdc06fb 100644 --- a/tests/src/test/java/org/teavm/vm/VMTest.java +++ b/tests/src/test/java/org/teavm/vm/VMTest.java @@ -23,10 +23,10 @@ import java.util.function.Supplier; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.interop.Async; +import org.teavm.interop.AsyncCallback; import org.teavm.jso.JSBody; import org.teavm.junit.SkipJVM; import org.teavm.junit.TeaVMTestRunner; -import org.teavm.platform.async.AsyncCallback; @RunWith(TeaVMTestRunner.class) public class VMTest {