diff --git a/teavm-classlib/pom.xml b/teavm-classlib/pom.xml index c3407f71d..9f638f16a 100644 --- a/teavm-classlib/pom.xml +++ b/teavm-classlib/pom.xml @@ -53,6 +53,7 @@ process-test-classes false + 1 diff --git a/teavm-core/src/main/java/org/teavm/common/FiniteExecutor.java b/teavm-core/src/main/java/org/teavm/common/FiniteExecutor.java new file mode 100644 index 000000000..24263f4e9 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/common/FiniteExecutor.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014 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.common; + +import java.util.concurrent.Executor; + +/** + * + * @author Alexey Andreev + */ +public interface FiniteExecutor extends Executor { + void complete(); + + void executeFast(Runnable runnable); +} diff --git a/teavm-core/src/main/java/org/teavm/common/SimpleFiniteExecutor.java b/teavm-core/src/main/java/org/teavm/common/SimpleFiniteExecutor.java new file mode 100644 index 000000000..953746cf4 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/common/SimpleFiniteExecutor.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014 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.common; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * + * @author Alexey Andreev + */ +public class SimpleFiniteExecutor implements FiniteExecutor { + private Queue queue = new LinkedList<>(); + + @Override + public void execute(Runnable command) { + queue.add(command); + } + + @Override + public void executeFast(Runnable runnable) { + execute(runnable); + } + + @Override + public void complete() { + while (!queue.isEmpty()) { + queue.remove().run(); + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/common/ThreadPoolFiniteExecutor.java b/teavm-core/src/main/java/org/teavm/common/ThreadPoolFiniteExecutor.java new file mode 100644 index 000000000..455915e8c --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/common/ThreadPoolFiniteExecutor.java @@ -0,0 +1,115 @@ +/* + * Copyright 2014 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.common; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * + * @author Alexey Andreev + */ +public class ThreadPoolFiniteExecutor implements FiniteExecutor { + private List threads = new ArrayList<>(); + private BlockingQueue queue = new LinkedBlockingQueue<>(); + private AtomicInteger runningTasks = new AtomicInteger(); + private final Object monitor = new Object(); + private AtomicReference thrownException = new AtomicReference<>(); + private ThreadLocal> localQueueues = new ThreadLocal<>(); + + public ThreadPoolFiniteExecutor(int numThreads) { + for (int i = 0; i < numThreads; ++i) { + Thread thread = new Thread() { + @Override public void run() { + takeTask(); + } + }; + threads.add(thread); + thread.start(); + } + } + + @Override + public void execute(Runnable command) { + runningTasks.incrementAndGet(); + try { + queue.put(command); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + @Override + public void executeFast(Runnable runnable) { + localQueueues.get().add(runnable); + } + + @Override + public void complete() { + synchronized (monitor) { + try { + monitor.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + if (thrownException.get() != null) { + throw thrownException.get(); + } + if (runningTasks.get() == 0) { + return; + } + } + } + + private void takeTask() { + Queue localQueue = new ArrayDeque<>(); + localQueueues.set(localQueue); + try { + while (true) { + Runnable task = queue.take(); + try { + task.run(); + while (!localQueue.isEmpty()) { + localQueue.remove().run(); + } + } catch (RuntimeException e) { + thrownException.set(e); + } finally { + if (runningTasks.decrementAndGet() == 0 || thrownException.get() != null) { + synchronized (monitor) { + monitor.notifyAll(); + } + } + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + public void stop() { + for (Thread thread : threads) { + thread.interrupt(); + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java index d806d2994..ac8056755 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java @@ -18,10 +18,10 @@ package org.teavm.dependency; import java.util.Collection; import java.util.HashSet; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import org.teavm.common.ConcurrentCachedMapper; +import org.teavm.common.FiniteExecutor; import org.teavm.common.Mapper; +import org.teavm.common.SimpleFiniteExecutor; import org.teavm.common.ConcurrentCachedMapper.KeyListener; import org.teavm.model.*; @@ -34,31 +34,21 @@ public class DependencyChecker { static final boolean shouldLog = System.getProperty("org.teavm.logDependencies", "false").equals("true"); private ClassHolderSource classSource; private ClassLoader classLoader; - private ScheduledThreadPoolExecutor executor; + private FiniteExecutor executor; private ConcurrentMap abstractMethods = new ConcurrentHashMap<>(); private ConcurrentCachedMapper methodCache; private ConcurrentCachedMapper fieldCache; private ConcurrentMap achievableClasses = new ConcurrentHashMap<>(); private ConcurrentMap initializedClasses = new ConcurrentHashMap<>(); - private AtomicReference exceptionOccured = new AtomicReference<>(); - private AtomicInteger activeTaskCount = new AtomicInteger(0); - private final Object activeTaskMonitor = new Object(); public DependencyChecker(ClassHolderSource classSource, ClassLoader classLoader) { - this(classSource, classLoader, Runtime.getRuntime().availableProcessors()); + this(classSource, classLoader, new SimpleFiniteExecutor()); } - public DependencyChecker(ClassHolderSource classSource, ClassLoader classLoader, int numThreads) { + public DependencyChecker(ClassHolderSource classSource, ClassLoader classLoader, FiniteExecutor executor) { this.classSource = classSource; this.classLoader = classLoader; - executor = new ScheduledThreadPoolExecutor(numThreads); - executor.setThreadFactory(new ThreadFactory() { - @Override public Thread newThread(Runnable r) { - Thread thread = new Thread(r); - thread.setDaemon(true); - return thread; - } - }); + this.executor = executor; methodCache = new ConcurrentCachedMapper<>(new Mapper() { @Override public MethodGraph map(MethodReference preimage) { return createMethodGraph(preimage); @@ -98,58 +88,15 @@ public class DependencyChecker { } public void schedulePropagation(final DependencyConsumer consumer, final String type) { - schedule(new Runnable() { + executor.executeFast(new Runnable() { @Override public void run() { consumer.consume(type); } }); } - void schedule(final Runnable runnable) { - synchronized (activeTaskMonitor) { - activeTaskCount.incrementAndGet(); - } - try { - executor.execute(new Runnable() { - @Override public void run() { - try { - runnable.run(); - } catch (RuntimeException e) { - activeTaskMonitor.notifyAll(); - exceptionOccured.compareAndSet(null, e); - executor.shutdownNow(); - } - synchronized (activeTaskMonitor) { - if (activeTaskCount.decrementAndGet() == 0) { - activeTaskMonitor.notifyAll(); - } - } - } - }); - } catch (RejectedExecutionException e) { - throw exceptionOccured.get(); - } - } - - public void checkDependencies() { - while (true) { - try { - synchronized (activeTaskMonitor) { - if (activeTaskCount.get() == 0 || exceptionOccured.get() != null) { - break; - } - activeTaskMonitor.wait(); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - RuntimeException e = exceptionOccured.get(); - if (e != null) { - throw exceptionOccured.get(); - } - executor.shutdown(); + public FiniteExecutor getExecutor() { + return executor; } boolean achieveClass(String className) { @@ -226,7 +173,7 @@ public class DependencyChecker { } final MethodGraph graph = new MethodGraph(parameterNodes, paramCount, resultNode, this); final MethodHolder currentMethod = method; - schedule(new Runnable() { + executor.execute(new Runnable() { @Override public void run() { DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(DependencyChecker.this); graphBuilder.buildGraph(currentMethod, graph); diff --git a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java index 4938ffcbd..ccbe610ac 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java +++ b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java @@ -21,10 +21,10 @@ import java.util.List; import java.util.Map; import java.util.Set; import org.teavm.codegen.*; +import org.teavm.common.FiniteExecutor; import org.teavm.dependency.DependencyChecker; import org.teavm.javascript.ast.ClassNode; import org.teavm.model.*; -import org.teavm.model.resource.ClasspathClassHolderSource; import org.teavm.model.util.*; import org.teavm.optimization.ClassSetOptimizer; @@ -35,25 +35,19 @@ import org.teavm.optimization.ClassSetOptimizer; public class JavascriptBuilder { private ClassHolderSource classSource; private DependencyChecker dependencyChecker; + private FiniteExecutor executor; private ClassLoader classLoader; private boolean minifying = true; - private boolean bytecodeLogging = true; + private boolean bytecodeLogging; private OutputStream logStream = System.out; private Map entryPoints = new HashMap<>(); private Map exportedClasses = new HashMap<>(); - public JavascriptBuilder(ClassHolderSource classSource, ClassLoader classLoader) { + JavascriptBuilder(ClassHolderSource classSource, ClassLoader classLoader, FiniteExecutor executor) { this.classSource = classSource; this.classLoader = classLoader; - dependencyChecker = new DependencyChecker(classSource, classLoader); - } - - public JavascriptBuilder(ClassLoader classLoader) { - this(new ClasspathClassHolderSource(classLoader), classLoader); - } - - public JavascriptBuilder() { - this(JavascriptBuilder.class.getClassLoader()); + dependencyChecker = new DependencyChecker(classSource, classLoader, executor); + this.executor = executor; } public boolean isMinifying() { @@ -107,7 +101,7 @@ public class JavascriptBuilder { ValueType.object("java.lang.Class")))); dependencyChecker.attachMethodGraph(new MethodReference("java.lang.String", new MethodDescriptor("", ValueType.arrayOf(ValueType.CHARACTER), ValueType.VOID))); - dependencyChecker.checkDependencies(); + executor.complete(); ListableClassHolderSource classSet = dependencyChecker.cutUnachievableClasses(); Decompiler decompiler = new Decompiler(classSet, classLoader); ClassSetOptimizer optimizer = new ClassSetOptimizer(); diff --git a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderFactory.java b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderFactory.java new file mode 100644 index 000000000..98f59399b --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderFactory.java @@ -0,0 +1,58 @@ +/* + * Copyright 2014 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.javascript; + +import org.teavm.common.FiniteExecutor; +import org.teavm.common.SimpleFiniteExecutor; +import org.teavm.model.ClassHolderSource; + +/** + * + * @author Alexey Andreev + */ +public class JavascriptBuilderFactory { + ClassHolderSource classSource; + ClassLoader classLoader; + FiniteExecutor executor = new SimpleFiniteExecutor(); + + public ClassHolderSource getClassSource() { + return classSource; + } + + public void setClassSource(ClassHolderSource classSource) { + this.classSource = classSource; + } + + public ClassLoader getClassLoader() { + return classLoader; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public FiniteExecutor getExecutor() { + return executor; + } + + public void setExecutor(FiniteExecutor executor) { + this.executor = executor; + } + + public JavascriptBuilder create() { + return new JavascriptBuilder(classSource, classLoader, executor); + } +} diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptJUnitMojo.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptJUnitMojo.java index 071f9a066..8f584f058 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptJUnitMojo.java +++ b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptJUnitMojo.java @@ -34,7 +34,11 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.junit.Test; +import org.teavm.common.FiniteExecutor; +import org.teavm.common.SimpleFiniteExecutor; +import org.teavm.common.ThreadPoolFiniteExecutor; import org.teavm.javascript.JavascriptBuilder; +import org.teavm.javascript.JavascriptBuilderFactory; import org.teavm.model.*; import org.teavm.model.resource.ClasspathClassHolderSource; @@ -68,6 +72,9 @@ public class BuildJavascriptJUnitMojo extends AbstractMojo { @Parameter private boolean minifying = true; + @Parameter + private int numThreads = 1; + public void setProject(MavenProject project) { this.project = project; } @@ -88,8 +95,13 @@ public class BuildJavascriptJUnitMojo extends AbstractMojo { this.minifying = minifying; } + public void setNumThreads(int numThreads) { + this.numThreads = numThreads; + } + @Override public void execute() throws MojoExecutionException, MojoFailureException { + Runnable finalizer = null; try { ClassLoader classLoader = prepareClassLoader(); getLog().info("Searching for tests in the directory `" + testFiles.getAbsolutePath() + "'"); @@ -146,14 +158,29 @@ public class BuildJavascriptJUnitMojo extends AbstractMojo { } int methodsGenerated = 0; log.info("Generating test files"); + FiniteExecutor executor = new SimpleFiniteExecutor(); + if (numThreads != 1) { + int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); + final ThreadPoolFiniteExecutor threadedExecutor = new ThreadPoolFiniteExecutor(threads); + finalizer = new Runnable() { + @Override public void run() { + threadedExecutor.stop(); + } + }; + executor = threadedExecutor; + } for (MethodReference method : testMethods) { log.debug("Building test for " + method); - decompileClassesForTest(classLoader, method, fileNames.get(method)); + decompileClassesForTest(classLoader, method, fileNames.get(method), executor); ++methodsGenerated; } log.info("Test files successfully generated for " + methodsGenerated + " method(s)"); } catch (IOException e) { throw new MojoFailureException("IO error occured generating JavaScript files", e); + } finally { + if (finalizer != null) { + finalizer.run(); + } } } @@ -189,9 +216,13 @@ public class BuildJavascriptJUnitMojo extends AbstractMojo { } } - private void decompileClassesForTest(ClassLoader classLoader, MethodReference methodRef, String targetName) - throws IOException { - JavascriptBuilder builder = new JavascriptBuilder(classLoader); + private void decompileClassesForTest(ClassLoader classLoader, MethodReference methodRef, String targetName, + FiniteExecutor executor) throws IOException { + JavascriptBuilderFactory builderFactory = new JavascriptBuilderFactory(); + builderFactory.setClassLoader(classLoader); + builderFactory.setClassSource(new ClasspathClassHolderSource(classLoader)); + builderFactory.setExecutor(executor); + JavascriptBuilder builder = builderFactory.create(); builder.setMinifying(minifying); File file = new File(outputDir, targetName); try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java index fc8867773..ac1f1f377 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java +++ b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java @@ -30,10 +30,13 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; +import org.teavm.common.ThreadPoolFiniteExecutor; import org.teavm.javascript.JavascriptBuilder; +import org.teavm.javascript.JavascriptBuilderFactory; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; +import org.teavm.model.resource.ClasspathClassHolderSource; /** * @@ -71,6 +74,9 @@ public class BuildJavascriptMojo extends AbstractMojo { @Parameter private boolean bytecodeLogging; + @Parameter(required = false) + private int numThreads = 1; + public void setProject(MavenProject project) { this.project = project; } @@ -99,13 +105,31 @@ public class BuildJavascriptMojo extends AbstractMojo { this.mainPageIncluded = mainPageIncluded; } + public void setNumThreads(int numThreads) { + this.numThreads = numThreads; + } + @Override public void execute() throws MojoExecutionException { Log log = getLog(); + Runnable finalizer = null; try { ClassLoader classLoader = prepareClassLoader(); log.info("Building JavaScript file"); - JavascriptBuilder builder = new JavascriptBuilder(classLoader); + JavascriptBuilderFactory builderFactory = new JavascriptBuilderFactory(); + builderFactory.setClassLoader(classLoader); + builderFactory.setClassSource(new ClasspathClassHolderSource(classLoader)); + if (numThreads != 1) { + int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); + final ThreadPoolFiniteExecutor executor = new ThreadPoolFiniteExecutor(threads); + finalizer = new Runnable() { + @Override public void run() { + executor.stop(); + } + }; + builderFactory.setExecutor(executor); + } + JavascriptBuilder builder = builderFactory.create(); builder.setMinifying(minifying); builder.setBytecodeLogging(bytecodeLogging); MethodDescriptor mainMethodDesc = new MethodDescriptor("main", ValueType.arrayOf( @@ -133,6 +157,10 @@ public class BuildJavascriptMojo extends AbstractMojo { throw new MojoExecutionException("Unexpected error occured", e); } catch (IOException e) { throw new MojoExecutionException("IO error occured", e); + } finally { + if (finalizer != null) { + finalizer.run(); + } } }