From b6cb9bfd4a45267190d8063f9fcfec7de2daf072 Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Sun, 15 Feb 2015 18:11:23 +0400 Subject: [PATCH] Fix monitor methods. Improve JSO to handle abstract classes --- .../lang/TIllegalMonitorStateException.java | 32 +++ .../org/teavm/classlib/java/lang/TObject.java | 198 +++++++++++------- .../org/teavm/classlib/java/lang/TThread.java | 13 +- .../java/org/teavm/javascript/Renderer.java | 13 ++ .../java/org/teavm/javascript/spi/Sync.java | 6 +- .../model/util/AsyncProgramSplitter.java | 10 + .../resources/org/teavm/javascript/runtime.js | 5 +- .../jso/plugin/JSODependencyListener.java | 2 +- .../jso/plugin/JSObjectClassTransformer.java | 3 + .../jso/plugin/JavascriptNativeProcessor.java | 58 +++++ .../NativeJavascriptClassRepository.java | 2 +- .../java/org/teavm/platform/Platform.java | 16 +- .../org/teavm/platform/PlatformHelper.java | 7 + .../org/teavm/platform/PlatformQueue.java | 55 +++++ .../org/teavm/platform/PlatformRunnable.java | 24 +++ .../platform/plugin/PlatformGenerator.java | 22 +- .../plugin/PlatformQueueGenerator.java | 45 ++++ .../org/teavm/samples/async/AsyncProgram.java | 26 +++ 18 files changed, 437 insertions(+), 100 deletions(-) create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TIllegalMonitorStateException.java create mode 100644 teavm-platform/src/main/java/org/teavm/platform/PlatformQueue.java create mode 100644 teavm-platform/src/main/java/org/teavm/platform/PlatformRunnable.java create mode 100644 teavm-platform/src/main/java/org/teavm/platform/plugin/PlatformQueueGenerator.java diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TIllegalMonitorStateException.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TIllegalMonitorStateException.java new file mode 100644 index 000000000..c77285633 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TIllegalMonitorStateException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang; + +/** + * + * @author Alexey Andreev + */ +public class TIllegalMonitorStateException extends TRuntimeException { + private static final long serialVersionUID = 7694307746228488658L; + + public TIllegalMonitorStateException() { + super(); + } + + public TIllegalMonitorStateException(TString message) { + super(message); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java index 71526720b..51a2f403b 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java @@ -16,14 +16,13 @@ package org.teavm.classlib.java.lang; import org.teavm.dom.browser.TimerHandler; -import org.teavm.dom.browser.Window; import org.teavm.javascript.spi.Async; import org.teavm.javascript.spi.Rename; import org.teavm.javascript.spi.Superclass; -import org.teavm.jso.JS; -import org.teavm.jso.JSArray; -import org.teavm.jso.JSObject; +import org.teavm.javascript.spi.Sync; import org.teavm.platform.Platform; +import org.teavm.platform.PlatformQueue; +import org.teavm.platform.PlatformRunnable; import org.teavm.platform.async.AsyncCallback; /** @@ -32,51 +31,97 @@ import org.teavm.platform.async.AsyncCallback; */ @Superclass("") public class TObject { - private static final Window window = (Window)JS.getGlobal(); - private JSArray notifyListeners; - private Lock lock; + Monitor monitor; - private static class Lock { + static class Monitor { + PlatformQueue enteringThreads; + PlatformQueue notifyListeners; TThread owner; int count; - public Lock() { + public Monitor() { this.owner = TThread.currentThread(); - count = 1; + enteringThreads = Platform.createQueue(); + notifyListeners = Platform.createQueue(); } } - interface NotifyListener extends JSObject { - boolean handleNotify(); + interface NotifyListener extends PlatformRunnable { + boolean expired(); } static void monitorEnter(TObject o) { - if (o.lock == null) { - o.lock = new Lock(); + monitorEnter(o, 1); + } + + @Async + static native void monitorEnter(TObject o, int count); + + static void monitorEnter(final TObject o, final int count, final AsyncCallback callback) { + if (o.monitor == null) { + o.monitor = new Monitor(); + } + if (o.monitor.owner == null) { + o.monitor.owner = TThread.currentThread(); + } + if (o.monitor.owner != TThread.currentThread()) { + final TThread thread = TThread.currentThread(); + o.monitor.enteringThreads.add(new PlatformRunnable() { + @Override public void run() { + TThread.setCurrentThread(thread); + o.monitor.owner = thread; + o.monitor.count += count; + callback.complete(null); + }; + }); + } else { + o.monitor.count += count; + callback.complete(null); + } + } + + @Sync + static void monitorExit(final TObject o) { + monitorExit(o, 1); + } + + @Sync + static void monitorExit(final TObject o, int count) { + if (o.isEmptyMonitor() || o.monitor.owner != TThread.currentThread()) { + throw new TIllegalMonitorStateException(); + } + o.monitor.count -= count; + if (o.monitor.count > 0) { return; } - if (o.lock.owner != TThread.currentThread()) { - while (o.lock != null) { - try { - o.lock.wait(); - } catch (InterruptedException ex) { + + o.monitor.owner = null; + Platform.startThread(new PlatformRunnable() { + @Override public void run() { + if (o.isEmptyMonitor() || o.monitor.owner != null) { + return; + } + if (!o.monitor.enteringThreads.isEmpty()) { + o.monitor.enteringThreads.remove().run(); } } - o.lock = new Lock(); + }); + } + + boolean isEmptyMonitor() { + if (monitor == null) { + return true; + } + if (monitor.owner == null && monitor.enteringThreads.isEmpty() && monitor.notifyListeners.isEmpty()) { + monitor = null; + return true; } else { - o.lock.count++; + return false; } } - static void monitorExit(TObject o){ - if (o.lock != null && o.lock.count-- == 0) { - o.lock.notifyAll(); - o.lock = null; - } - } - - static boolean holdsLock(TObject o){ - return o.lock != null && o.lock.owner == TThread.currentThread(); + static boolean holdsLock(TObject o) { + return o.monitor != null && o.monitor.owner == TThread.currentThread(); } @Rename("fakeInit") @@ -123,30 +168,35 @@ public class TObject { return result; } + @Sync @Rename("notify") public final void notify0() { + if (!holdsLock(this)) { + throw new TIllegalMonitorStateException(); + } TThread thread = TThread.currentThread(); - if (notifyListeners != null) { - while (notifyListeners.getLength() > 0 && notifyListeners.shift().handleNotify()) { - // repeat loop - } - if (notifyListeners.getLength() == 0) { - notifyListeners = null; + PlatformQueue listeners = monitor.notifyListeners; + while (!listeners.isEmpty()) { + NotifyListener listener = listeners.remove(); + if (!listener.expired()) { + Platform.startThread(listener); + break; } } TThread.setCurrentThread(thread); } + @Sync @Rename("notifyAll") - public final void notifyAll0(){ - if (notifyListeners != null){ - JSArray listeners = window.newArray(); - while (notifyListeners.getLength() > 0) { - listeners.push(notifyListeners.shift()); - } - notifyListeners = null; - while (listeners.getLength() > 0) { - listeners.shift().handleNotify(); + public final void notifyAll0() { + if (!holdsLock(this)) { + throw new TIllegalMonitorStateException(); + } + PlatformQueue listeners = monitor.notifyListeners; + while (!listeners.isEmpty()) { + NotifyListener listener = listeners.remove(); + if (!listener.expired()) { + Platform.startThread(listener); } } } @@ -166,49 +216,51 @@ public class TObject { @Rename("wait") public final void wait0(long timeout, int nanos, final AsyncCallback callback) { - if (notifyListeners == null) { - notifyListeners = window.newArray(); + final NotifyListenerImpl listener = new NotifyListenerImpl(this, callback, monitor.count); + monitor.notifyListeners.add(listener); + if (timeout > 0 || nanos > 0) { + listener.timerId = Platform.schedule(listener, timeout >= Integer.MAX_VALUE ? Integer.MAX_VALUE : + (int)timeout); } - final NotifyListenerImpl listener = new NotifyListenerImpl(callback); - notifyListeners.push(listener); - if (timeout == 0 && nanos == 0) { - return; - } - listener.timerId = window.setTimeout(listener, timeout); + monitorExit(this, monitor.count); } - private static class NotifyListenerImpl implements NotifyListener, TimerHandler { + private static class NotifyListenerImpl implements NotifyListener, TimerHandler, PlatformRunnable { + final TObject obj; final AsyncCallback callback; final TThread currentThread = TThread.currentThread(); int timerId = -1; - boolean finished; + boolean expired; + int lockCount; - public NotifyListenerImpl(AsyncCallback callback) { + public NotifyListenerImpl(TObject obj, AsyncCallback callback, int lockCount) { + this.obj = obj; this.callback = callback; + this.lockCount = lockCount; } @Override - public boolean handleNotify() { - if (finished) { - return false; - } - TThread.setCurrentThread(currentThread); - if (timerId >= 0) { - window.clearTimeout(timerId); - timerId = -1; - } - finished = true; - try { - callback.complete(null); - } finally { - TThread.setCurrentThread(TThread.getMainThread()); - } - return true; + public boolean expired() { + boolean result = expired; + expired = true; + return result; } @Override public void onTimer() { - handleNotify(); + if (!expired()) { + Platform.startThread(this); + } + } + + @Override + public void run() { + if (timerId >= 0) { + Platform.killSchedule(timerId); + timerId = -1; + } + TThread.setCurrentThread(currentThread); + monitorEnter(obj, lockCount, callback); } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java index 43db2182f..807c74f3c 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java @@ -20,6 +20,7 @@ import org.teavm.dom.browser.Window; import org.teavm.javascript.spi.Async; import org.teavm.jso.JS; import org.teavm.platform.Platform; +import org.teavm.platform.PlatformRunnable; import org.teavm.platform.async.AsyncCallback; @@ -57,7 +58,7 @@ public class TThread extends TObject implements TRunnable { } public void start(){ - Platform.startThread(new Runnable() { + Platform.startThread(new PlatformRunnable() { @Override public void run() { try { @@ -98,14 +99,7 @@ public class TThread extends TObject implements TRunnable { public static native void yield(); private static void yield(final AsyncCallback callback) { - final TThread current = currentThread(); - window.setTimeout(new TimerHandler() { - @Override public void onTimer() { - setCurrentThread(current); - callback.complete(null); - setCurrentThread(mainThread); - } - }, 0); + callback.complete(null); } public void interrupt() { @@ -140,7 +134,6 @@ public class TThread extends TObject implements TRunnable { @Override public void onTimer() { setCurrentThread(current); callback.complete(null); - setCurrentThread(mainThread); } }, millis); } diff --git a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java index 71eb43831..1a55f31dd 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -205,6 +205,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext renderRuntimeObjcls(); renderRuntimeNullCheck(); renderRuntimeIntern(); + renderRuntimeThreads(); } catch (NamingException e) { throw new RenderingException("Error rendering runtime methods. See a cause for details", e); } catch (IOException e) { @@ -270,6 +271,18 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append("function $rt_objcls() { return ").appendClass("java.lang.Object").append("; }").newLine(); } + private void renderRuntimeThreads() throws IOException { + writer.append("function $rt_getThread()").ws().append("{").indent().softNewLine(); + writer.append("return ").appendMethodBody(Thread.class, "currentThread", Thread.class).append("();") + .softNewLine(); + writer.outdent().append("}").newLine(); + + writer.append("function $rt_setThread(t)").ws().append("{").indent().softNewLine(); + writer.append("return ").appendMethodBody(Thread.class, "setCurrentThread", Thread.class, void.class) + .append("(t);").softNewLine(); + writer.outdent().append("}").newLine(); + } + public void render(List classes) throws RenderingException { for (ClassNode cls : classes) { renderDeclaration(cls); diff --git a/teavm-core/src/main/java/org/teavm/javascript/spi/Sync.java b/teavm-core/src/main/java/org/teavm/javascript/spi/Sync.java index d43b1acf4..27d9be337 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/spi/Sync.java +++ b/teavm-core/src/main/java/org/teavm/javascript/spi/Sync.java @@ -15,10 +15,7 @@ */ package org.teavm.javascript.spi; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; /** * @@ -26,5 +23,6 @@ import java.lang.annotation.Target; */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) +@Inherited public @interface Sync { } diff --git a/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java index 16c02e49d..23ce1325f 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java +++ b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java @@ -35,6 +35,9 @@ public class AsyncProgramSplitter { } public void split(Program program) { + System.out.println("Splitting program"); + System.out.println(new ListingBuilder().buildListing(program, " ")); + parts.clear(); Program initialProgram = createStubCopy(program); Part initialPart = new Part(); @@ -145,6 +148,13 @@ public class AsyncProgramSplitter { } } + for (int i = 0; i < parts.size(); ++i) { + Program part = parts.get(i).program; + System.out.println("Part " + i); + System.out.println(new ListingBuilder().buildListing(part, " ")); + } + System.out.println(); + partMap.clear(); } diff --git a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js index 35544d4de..d81aeb0fa 100644 --- a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js +++ b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js @@ -464,8 +464,9 @@ function $rt_asyncAdapter(f) { var result; var args = Array.prototype.slice.apply(arguments); var $return = args.pop(); + args.unshift(this); try { - result = f.apply(this, args); + result = f.apply(null, args); } catch (e) { return $return($rt_asyncError(e)); } @@ -510,7 +511,9 @@ function $rt_continue(f) { return function() { var self = this; var args = arguments; + var thread = $rt_getThread(); setTimeout(function() { + $rt_setThread(thread); f.apply(self, args); }, 0); }; diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSODependencyListener.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSODependencyListener.java index 66d3d3f8a..75ec8f361 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSODependencyListener.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSODependencyListener.java @@ -132,7 +132,7 @@ class JSODependencyListener implements DependencyListener { } @Override - public void methodAchieved(DependencyAgent agent, MethodDependency method, CallLocation location) { + public void methodAchieved(DependencyAgent agent, MethodDependency methodDep, CallLocation location) { } @Override diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java index 738fa639d..acc7fa125 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java @@ -31,6 +31,9 @@ public class JSObjectClassTransformer implements ClassHolderTransformer { processor = new JavascriptNativeProcessor(innerSource); processor.setDiagnostics(diagnostics); processor.processClass(cls); + if (processor.isNative(cls.getName())) { + processor.processFinalMethods(cls); + } for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { if (method.getAnnotations().get(JSBody.class.getName()) != null) { processor.processJSBody(cls, method); diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java index 8f2f1f47c..e1a5b3274 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java @@ -21,6 +21,8 @@ import org.teavm.javascript.spi.GeneratedBy; import org.teavm.jso.*; import org.teavm.model.*; import org.teavm.model.instructions.*; +import org.teavm.model.util.InstructionVariableMapper; +import org.teavm.model.util.ProgramUtils; /** * @@ -39,6 +41,10 @@ class JavascriptNativeProcessor { nativeRepos = new NativeJavascriptClassRepository(classSource); } + public boolean isNative(String className) { + return nativeRepos.isJavaScriptClass(className); + } + public void setDiagnostics(Diagnostics diagnostics) { this.diagnostics = diagnostics; } @@ -62,6 +68,47 @@ class JavascriptNativeProcessor { } } + public void processFinalMethods(ClassHolder cls) { + // TODO: don't allow final methods to override anything + for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { + if (method.hasModifier(ElementModifier.FINAL) && method.getProgram() != null) { + ValueType[] staticSignature = getStaticSignature(method.getReference()); + MethodHolder callerMethod = new MethodHolder(new MethodDescriptor(method.getName() + "$static", + staticSignature)); + callerMethod.getModifiers().add(ElementModifier.STATIC); + final Program program = ProgramUtils.copy(method.getProgram()); + program.createVariable(); + InstructionVariableMapper variableMapper = new InstructionVariableMapper() { + @Override protected Variable map(Variable var) { + return program.variableAt(var.getIndex() + 1); + } + }; + for (int i = program.variableCount() - 1; i > 0; --i) { + program.variableAt(i).getDebugNames().addAll(program.variableAt(i - 1).getDebugNames()); + program.variableAt(i - 1).getDebugNames().clear(); + } + for (int i = 0; i < program.basicBlockCount(); ++i) { + BasicBlock block = program.basicBlockAt(i); + for (Instruction insn : block.getInstructions()) { + insn.acceptVisitor(variableMapper); + } + } + callerMethod.setProgram(program); + cls.addMethod(callerMethod); + } + } + } + + private static ValueType[] getStaticSignature(MethodReference method) { + ValueType[] signature = method.getSignature(); + ValueType[] staticSignature = new ValueType[signature.length + 1]; + for (int i = 0; i < signature.length; ++i) { + staticSignature[i + 1] = signature[i]; + } + staticSignature[0] = ValueType.object(method.getClassName()); + return staticSignature; + } + public void processProgram(MethodHolder methodToProcess) { program = methodToProcess.getProgram(); for (int i = 0; i < program.basicBlockCount(); ++i) { @@ -78,6 +125,17 @@ class JavascriptNativeProcessor { } replacement.clear(); MethodReader method = getMethod(invoke.getMethod()); + if (method.hasModifier(ElementModifier.STATIC)) { + continue; + } + if (method.hasModifier(ElementModifier.FINAL)) { + invoke.setMethod(new MethodReference(method.getOwnerName(), method.getName() + "$static", + getStaticSignature(method.getReference()))); + invoke.setType(InvocationType.SPECIAL); + invoke.getArguments().add(0, invoke.getInstance()); + invoke.setInstance(null); + continue; + } CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation()); if (method.getAnnotations().get(JSProperty.class.getName()) != null) { if (isProperGetter(method.getDescriptor())) { diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/NativeJavascriptClassRepository.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/NativeJavascriptClassRepository.java index 302b2ab34..a83959b8e 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/NativeJavascriptClassRepository.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/NativeJavascriptClassRepository.java @@ -46,7 +46,7 @@ class NativeJavascriptClassRepository { private boolean figureOutIfJavaScriptClass(String className) { ClassReader cls = classSource.get(className); - if (cls == null || !cls.hasModifier(ElementModifier.INTERFACE)) { + if (cls == null || !(cls.hasModifier(ElementModifier.INTERFACE) || cls.hasModifier(ElementModifier.ABSTRACT))) { return false; } for (String iface : cls.getInterfaces()) { diff --git a/teavm-platform/src/main/java/org/teavm/platform/Platform.java b/teavm-platform/src/main/java/org/teavm/platform/Platform.java index 8e9976669..2a11be646 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/Platform.java +++ b/teavm-platform/src/main/java/org/teavm/platform/Platform.java @@ -94,12 +94,24 @@ public final class Platform { @GeneratedBy(PlatformGenerator.class) @PluggableDependency(PlatformGenerator.class) - public static native void startThread(Runnable runnable); + public static native void startThread(PlatformRunnable runnable); - private static void launchThread(Runnable runnable) { + private static void launchThread(PlatformRunnable runnable) { runnable.run(); } + @GeneratedBy(PlatformGenerator.class) + @PluggableDependency(PlatformGenerator.class) + public static native int schedule(PlatformRunnable runnable, int timeout); + + public static void killSchedule(int id) { + ((PlatformHelper)JS.getGlobal()).killSchedule(id); + } + + public static PlatformQueue createQueue() { + return ((PlatformHelper)JS.getGlobal()).newQueue(); + } + public static PlatformString stringFromCharCode(int charCode) { return ((PlatformHelper)JS.getGlobal()).getStringClass().fromCharCode(charCode); } diff --git a/teavm-platform/src/main/java/org/teavm/platform/PlatformHelper.java b/teavm-platform/src/main/java/org/teavm/platform/PlatformHelper.java index 48f851b2c..817cc08e3 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/PlatformHelper.java +++ b/teavm-platform/src/main/java/org/teavm/platform/PlatformHelper.java @@ -15,6 +15,7 @@ */ package org.teavm.platform; +import org.teavm.jso.JSConstructor; import org.teavm.jso.JSMethod; import org.teavm.jso.JSObject; import org.teavm.jso.JSProperty; @@ -29,4 +30,10 @@ interface PlatformHelper extends JSObject { @JSProperty("String") PlatformStringClass getStringClass(); + + @JSConstructor("Array") + PlatformQueue newQueue(); + + @JSMethod("clearTimeout") + void killSchedule(int scheduleId); } diff --git a/teavm-platform/src/main/java/org/teavm/platform/PlatformQueue.java b/teavm-platform/src/main/java/org/teavm/platform/PlatformQueue.java new file mode 100644 index 000000000..91ae1514e --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/PlatformQueue.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015 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; + +import org.teavm.dependency.PluggableDependency; +import org.teavm.javascript.spi.InjectedBy; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.platform.plugin.PlatformQueueGenerator; + +/** + * + * @author Alexey Andreev + */ +public abstract class PlatformQueue implements JSObject { + @JSProperty + public abstract int getLength(); + + public final boolean isEmpty() { + return getLength() == 0; + } + + abstract void push(PlatformObject obj); + + abstract PlatformObject shift(); + + public final void add(T e) { + push(wrap(e)); + } + + public final T remove() { + return unwrap(shift()); + } + + private static PlatformObject wrap(Object obj) { + return Platform.getPlatformObject(obj); + } + + @InjectedBy(PlatformQueueGenerator.class) + @PluggableDependency(PlatformQueueGenerator.class) + private static native S unwrap(PlatformObject obj); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/PlatformRunnable.java b/teavm-platform/src/main/java/org/teavm/platform/PlatformRunnable.java new file mode 100644 index 000000000..1ca038368 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/PlatformRunnable.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015 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; + +/** + * + * @author Alexey Andreev + */ +public interface PlatformRunnable { + void run(); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java index fb5d9bfc9..8085d7263 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java @@ -26,6 +26,7 @@ import org.teavm.javascript.spi.Injector; import org.teavm.javascript.spi.InjectorContext; import org.teavm.model.*; import org.teavm.platform.Platform; +import org.teavm.platform.PlatformRunnable; /** * @@ -41,9 +42,10 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin case "clone": method.getVariable(1).connect(method.getResult()); break; - case "startThread": { + case "startThread": + case "schedule": { MethodDependency launchMethod = agent.linkMethod(new MethodReference(Platform.class, - "launchThread", Runnable.class, void.class), null); + "launchThread", PlatformRunnable.class, void.class), null); method.getVariable(1).connect(launchMethod.getVariable(1)); launchMethod.use(); break; @@ -78,7 +80,10 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin generateClone(context, writer); break; case "startThread": - generateStartThread(context, writer); + generateSchedule(context, writer, false); + break; + case "schedule": + generateSchedule(context, writer, true); break; } } @@ -129,11 +134,12 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin writer.append("return copy;").softNewLine(); } - private void generateStartThread(GeneratorContext context, SourceWriter writer) throws IOException { + private void generateSchedule(GeneratorContext context, SourceWriter writer, boolean timeout) throws IOException { String runnable = context.getParameterName(1); - writer.append("window.setTimeout(function()").ws().append("{").indent().softNewLine(); - writer.append("$rt_rootInvocationAdapter(").appendMethodBody(Platform.class, "launchThread", Runnable.class, - void.class).append(")(").append(runnable).append(");").softNewLine(); - writer.outdent().append("},").ws().append("0);").softNewLine(); + writer.append("return window.setTimeout(function()").ws().append("{").indent().softNewLine(); + writer.append("$rt_rootInvocationAdapter(").appendMethodBody(Platform.class, "launchThread", + PlatformRunnable.class, void.class).append(")(").append(runnable).append(");").softNewLine(); + writer.outdent().append("},").ws().append(timeout ? context.getParameterName(2) : "0") + .append(");").softNewLine(); } } diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/PlatformQueueGenerator.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/PlatformQueueGenerator.java new file mode 100644 index 000000000..1f358308c --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/PlatformQueueGenerator.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 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.io.IOException; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyPlugin; +import org.teavm.dependency.MethodDependency; +import org.teavm.javascript.spi.Injector; +import org.teavm.javascript.spi.InjectorContext; +import org.teavm.model.CallLocation; +import org.teavm.model.MethodReference; +import org.teavm.platform.PlatformObject; +import org.teavm.platform.PlatformQueue; + +/** + * + * @author Alexey Andreev + */ +public class PlatformQueueGenerator implements Injector, DependencyPlugin { + @Override + public void methodAchieved(DependencyAgent agent, MethodDependency method, CallLocation location) { + MethodDependency addMethod = agent.linkMethod(new MethodReference(PlatformQueue.class, "wrap", + Object.class, PlatformObject.class), null); + addMethod.getVariable(1).connect(method.getResult()); + } + + @Override + public void generate(InjectorContext context, MethodReference methodRef) throws IOException { + context.writeExpr(context.getArgument(0)); + } +} diff --git a/teavm-samples/teavm-samples-async/src/main/java/org/teavm/samples/async/AsyncProgram.java b/teavm-samples/teavm-samples-async/src/main/java/org/teavm/samples/async/AsyncProgram.java index 8264ea2e6..c21160c6e 100644 --- a/teavm-samples/teavm-samples-async/src/main/java/org/teavm/samples/async/AsyncProgram.java +++ b/teavm-samples/teavm-samples-async/src/main/java/org/teavm/samples/async/AsyncProgram.java @@ -29,6 +29,7 @@ public final class AsyncProgram { public static void main(String[] args) throws InterruptedException { report(Arrays.toString(args)); + findPrimes(); withoutAsync(); report(""); withAsync(); @@ -64,11 +65,36 @@ public final class AsyncProgram { report("Now trying wait..."); synchronized (lock) { + report("Lock acquired"); lock.wait(20000); } report("Finished main thread"); } + private static void findPrimes() { + report("Finding primes"); + boolean[] prime = new boolean[1000]; + prime[2] = true; + prime[3] = true; + nextPrime: for (int i = 5; i < prime.length; i += 2) { + int maxPrime = (int)Math.sqrt(i); + for (int j = 3; j <= maxPrime; j += 2) { + Thread.yield(); + if (prime[j] && i % j == 0) { + continue nextPrime; + } + } + prime[i] = true; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 100; ++i) { + if (prime[i]) { + sb.append(i).append(' '); + } + } + report(sb.toString()); + } + private static void report(String message) { long current = System.currentTimeMillis() - start; System.out.println("[" + Thread.currentThread().getName() + "]/" + current + ": " + message);