diff --git a/ChangeLog b/ChangeLog index 7084fbcb00597f4dc835967f18b44307d8dccd57..8a4d8d610e8a0a4d8554b18b9d18abd8472973bb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2017-08-29 Jesper Öqvist <jesper.oqvist@cs.lth.se> + + * Added the --concurrent option to generate thread-safe attribute evaluation code. + * Added possibility to parallelize collection attributes using annotations. + The @Parallel annotation causes the collection phase to be parallelized, + and the @ParallelSurvey annotation causes the survey phase to be parallelized. + The number of threads used in parallel collection attribute evaluation is + controlled using the --num_threads option. + 2017-03-01 Jesper Öqvist <jesper.oqvist@cs.lth.se> * Upgraded bootstrapping JastAdd version from 2.2.0 to 2.2.2. diff --git a/src/jastadd/ast/Annotations.jrag b/src/jastadd/ast/Annotations.jrag index 668c05c6a2040aa06b19a45f65cc5a533c8dcdc3..c6899935015bc22dbb135f6c8500896f7b5d9a39 100644 --- a/src/jastadd/ast/Annotations.jrag +++ b/src/jastadd/ast/Annotations.jrag @@ -58,7 +58,9 @@ aspect Annotations { || a.isAnnotation("@LazyCondition") || a.isAnnotation("@Circular") || a.isAnnotation("@CollectionGroup") - || a.isAnnotation("@Naive"); + || a.isAnnotation("@Naive") + || a.isAnnotation("@Parallel") + || a.isAnnotation("@ParallelSurvey"); } /** diff --git a/src/jastadd/ast/CollectionAttributes.jrag b/src/jastadd/ast/CollectionAttributes.jrag index 2e7c374a32696775a42f7941a821d94660fb622b..c4ca0f82ac98782293ffdf89c664fe86044d9a2a 100644 --- a/src/jastadd/ast/CollectionAttributes.jrag +++ b/src/jastadd/ast/CollectionAttributes.jrag @@ -83,6 +83,20 @@ aspect CollectionAttributes { /** @return {@code true} if this contribution implicitly contributes to the collection root. */ syn boolean CollEq.implicitTarget() = getReference().isEmpty(); + /** + * This gives the element type of the collection attribute's collection type. + * + * Note: only uses the declared typename for the collection attribute, can't handle + * for example a subclass of {@code List<Foo>}. + */ + syn String CollDecl.componentType() { + String type = config().astNodeType(); + if (getType().indexOf('<') < getType().indexOf('>')) { + type = getType().substring(getType().indexOf('<') + 1, getType().indexOf('>')); + } + return type; + } + /** * Find interface collection attribute declarations. */ @@ -171,15 +185,22 @@ aspect CollectionAttributes { * * <p>Defaults to {@code true}. */ - syn boolean AttrDecl.lazyCondition() = hasAnnotation("@LazyCondition"); + syn boolean AttrDecl.lazyCondition() = + hasAnnotation("@LazyCondition") + || hasAnnotation("@Parallel"); + syn boolean AttrEq.lazyCondition() = decl().lazyCondition(); + syn boolean AttrDecl.parallel() = hasAnnotation("@Parallel"); + + syn boolean AttrDecl.parallelSurvey() = hasAnnotation("@ParallelSurvey"); + /** * One phase evaluation combines the survey and combination phases. * * <p>Defaults to {@code false}. */ - syn boolean AttrDecl.onePhase() = hasAnnotation("@OnePhase"); + syn boolean AttrDecl.onePhase() = hasAnnotation("@OnePhase") && !config().concurrentEval(); syn boolean AttrEq.onePhase() = decl().onePhase(); /** @@ -341,6 +362,8 @@ aspect CollectionAttributes { } if (skipSuperCall) { out.println(config().indent + "}"); + } else if (isASTNodeDecl() && !decl.parallelSurvey()) { + decl.templateContext().expand("CollDecl.collectContributors:default", out); } else { if (isASTNodeDecl()) { decl.templateContext().expand("CollDecl.collectContributors:default", out); diff --git a/src/jastadd/ast/Flush.jrag b/src/jastadd/ast/Flush.jrag index 895b1f895c9d51608a51d907e4b816b316ecd353..5cbd182b8f4c40d5aee1e679be3aee15d32e584f 100644 --- a/src/jastadd/ast/Flush.jrag +++ b/src/jastadd/ast/Flush.jrag @@ -41,8 +41,15 @@ aspect Flush { for (AttrDecl attr : listOfCachedAttributes()) { sb.append(attr.signature() + "_reset();\n"); } - tt.bind("FlushAttrCacheBody", sb.toString()); + + StringBuilder fresh = new StringBuilder(); + for (Iterator itr = listOfCachedAttributes().iterator(); itr.hasNext();) { + AttrDecl attr = (AttrDecl) itr.next(); + fresh.append(attr.signature() + "_fresh();\n"); + } + tt.bind("MakeFreshNodeBody", fresh.toString()); + tt.expand("ASTDecl.flushAttrCacheMethod", out); tt.expand("ASTDecl.flushCollectionCacheMethod", out); } diff --git a/src/jastadd/ast/JragCodeGen.jrag b/src/jastadd/ast/JragCodeGen.jrag index ea6f7b2ed88bf465d99ca7e83f3237d25be3e885..1561059add4b4f508f40f1feb9f0749f277f0d25 100644 --- a/src/jastadd/ast/JragCodeGen.jrag +++ b/src/jastadd/ast/JragCodeGen.jrag @@ -50,6 +50,16 @@ aspect AttributeKind { aspect JragCodeGen { + /** + * When generating concurrent evaluation code, the collection survey phase needs to + * be synchronized for parallelized collection attributes. + * + * <p>Note: collection attributes are not parallelized unless specified by annotation. + */ + syn String AttrDecl.synchronizedModifier() = ""; + eq CollDecl.synchronizedModifier() = + config().concurrentEval() && (parallel() || parallelSurvey()) ? "synchronized " : ""; + /** * Generate the declaredat documentation tag. If no file name is available * then no tag is generated. @@ -111,6 +121,21 @@ aspect JragCodeGen { public String Grammar.genImportsList() { Set imports = new LinkedHashSet(); + if (config().concurrentEval()) { + // Extra import declarations needed for concurrent code generation. + imports.add("import java.util.concurrent.atomic.AtomicInteger;"); + imports.add("import java.util.concurrent.atomic.AtomicReference;"); + imports.add("import java.util.concurrent.Future;"); + imports.add("import java.util.concurrent.Executors;"); + imports.add("import java.util.concurrent.ExecutorService;"); + imports.add("import java.util.concurrent.ExecutionException;"); + imports.add("import java.util.concurrent.Callable;"); + imports.add("import java.util.concurrent.ConcurrentMap;"); + imports.add("import java.util.concurrent.ConcurrentHashMap;"); + imports.add("import java.util.ArrayList;"); + imports.add("import java.util.LinkedList;"); + imports.add("import java.util.Collection;"); + } for (org.jastadd.jrag.AST.ASTCompilationUnit u : compilationUnits) { imports.addAll(Unparser.getImports(u)); } @@ -299,6 +324,33 @@ aspect JragCodeGen { syn boolean TokenComponent.isPrimitive() = AttrDecl.isPrimitiveType(getTokenId().getTYPE()); + /** + * @return {@code true} if this attribute returns only non-nullable values. + */ + syn boolean AttrDecl.isNonNull() = isPrimitive(); // || declaredNonNull(); // TODO: implement annotation. + + /** + * Returns {@code true} if the result of this attribute is always identical + * when evaluated multiple times. This is true for primitive attributes and + * pure reference attributes, but not for attributes that construct a new + * object as the result. + */ + syn boolean AttrDecl.isIdentityComparable() = + !(declaredNTA() || isAttrNTA()) && (isPrimitive() || isReferenceAttribute()); + + /** + * @return {@code true} if the return type is among the AST node types in the abstract grammar. + */ + syn boolean AttrDecl.isReferenceAttribute() { + if (!isPrimitive()) { + TypeDecl typeDecl = grammar().lookup(getType()); + if (typeDecl instanceof ASTDecl) { + return true; + } + } + return false; + } + public String AttrDecl.parameterStructure() { if (!isParameterized() || (!isMemoized() && !config().visitCheckEnabled())) { return ""; @@ -498,14 +550,14 @@ aspect JragCodeGen { /** * Generate code to test if two attribute values differ based on the type of the attribute. */ - public String AttrDecl.valueComparisonExpr(String oldValue, String newValue) { - if (isPrimitive() || isCircularRewrite()) { + public String AttrDecl.valueComparisonExpr(String newValue, String oldValue) { + if (isPrimitive()) { return String.format("%s != %s", oldValue, newValue); - } else if (declaredNTA() && isCircular()) { - return String.format("!is$Equal(%s, %s)", oldValue, newValue); + } else if (isCircularRewrite()) { + return String.format("%s != %s || %s.canRewrite()", oldValue, newValue, newValue); } else { - return String.format("(%s == null && %s != null) || (%s != null && !%s.equals(%s))", - oldValue, newValue, oldValue, oldValue, newValue); + return String.format("!AttributeValue.equals(%s, %s)", + oldValue, newValue); } } @@ -526,7 +578,7 @@ aspect JragCodeGen { } else { // Parameterized, circular attribute. attr.emitResetMethod(out); - if (attr.declaredNTA()) { + if (attr.declaredNTA() || (config().concurrentEval() && attr.isCircular())) { attr.emitCacheDeclarations(out); } else if (config().lazyMaps()) { out.format("%sprotected %s %s_values;%n", @@ -677,7 +729,11 @@ aspect JragCodeGen { if (comp.name().equals(attrName) && comp instanceof AggregateComponentNTA || attrName.equals(comp.name() + "Opt") && comp instanceof OptionalComponentNTA || attrName.equals(comp.name() + "List") && comp instanceof ListComponentNTA) { - return "setChild(" + signature() + "_value, get" + attrName + "ChildPosition());\n"; + if (config().concurrentEval()) { + return "setChild(_result, get" + attrName + "ChildPosition());\n"; + } else { + return "setChild(" + signature() + "_value, get" + attrName + "ChildPosition());\n"; + } } // Token components are not stored in child vector. return ""; @@ -843,4 +899,6 @@ aspect Compute { syn boolean AttrDecl.simpleCacheCheck() = !config().safeLazy() || isCircular() || declaredNTA() || isAttrNTA(); + + syn boolean AttrDecl.cacheInCycle() = !simpleCacheCheck(); } diff --git a/src/jastadd/ast/Rewrites.jrag b/src/jastadd/ast/Rewrites.jrag index 60db679efc41dc700a1fb96b4f811c1335567ac7..7c28b3136de3b27f598906e0e81e087ca05bd00d 100644 --- a/src/jastadd/ast/Rewrites.jrag +++ b/src/jastadd/ast/Rewrites.jrag @@ -168,9 +168,16 @@ aspect Rewrites { // this node inherites the rewrittenNode attribute). CircularRewriteDecl decl = getCircularRewriteDecl(); TemplateContext tt = decl.templateContext(); - String newValue = "new_" + decl.signature() + "_value"; - String oldValue = decl.signature() + "_value"; - tt.bind("CircularComputeRhs", oldValue + ".rewriteTo()"); + String newValue, oldValue; + if (config().concurrentEval()) { + newValue = "_next"; + oldValue = "_previous.value"; + tt.bind("CircularComputeRhs", "((" + config().astNodeType() + ") " + decl.signature() + "_value.get()).rewriteTo()"); + } else { + newValue = "new_" + decl.signature() + "_value"; + oldValue = decl.signature() + "_value"; + tt.bind("CircularComputeRhs", oldValue + ".rewriteTo()"); + } tt.bind("ChangeCondition", String.format("%s != %s || %s.canRewrite()", newValue, oldValue, newValue)); tt.bind("BottomValue", "this"); @@ -189,6 +196,10 @@ aspect Rewrites { } } + syn boolean AttrDecl.isCircularNta() = false; + + eq CircularRewriteDecl.isCircularNta() = true; + syn lazy CircularRewriteDecl ASTDecl.getCircularRewriteDecl() = new CircularRewriteDecl( new List<Parameter>(), diff --git a/src/jastadd/core/TemplateUtil.jrag b/src/jastadd/core/TemplateUtil.jrag index 7496617de1bcac7df13eb799072c58c7e1d87781..8b8c24dacffacc11116eb3419dcaf21cbe5887fb 100644 --- a/src/jastadd/core/TemplateUtil.jrag +++ b/src/jastadd/core/TemplateUtil.jrag @@ -86,6 +86,14 @@ aspect TemplateUtil { loadTemplates(tt, "trace/Tracer"); loadTemplates(tt, "trace/TraceHooks"); + if (config().concurrentEval()) { + loadTemplates(tt, "concurrent/Attributes"); + loadTemplates(tt, "concurrent/InheritedAttributes"); + loadTemplates(tt, "concurrent/Collections"); + loadTemplates(tt, "concurrent/Flush"); + loadTemplates(tt, "concurrent/Circular"); + loadTemplates(tt, "concurrent/Trace"); + } return new SimpleContext(tt, this); } diff --git a/src/java/org/jastadd/Configuration.java b/src/java/org/jastadd/Configuration.java index ae16017defc6bbf4a035e985cac93f027084ca99..e03efa39dc527e25bb66350a99ee47b84c9f889c 100644 --- a/src/java/org/jastadd/Configuration.java +++ b/src/java/org/jastadd/Configuration.java @@ -387,6 +387,22 @@ public class Configuration { "safe in-cycle caching of non-circular attributes") .nonStandard(); + Option<Boolean> concurrentOption = new BooleanOption("concurrent", + "generate concurrent attribute evaluation code") + .templateVariable("Concurrent"); + + Option<String> numThreadsOption = new ValueOption("num_threads", + "number of parallel threads to use for parallelized attributes") + .acceptAnyValue() + .defaultValue("4") + .templateVariable("NumThreads"); + + Option<String> concurrentMap = new ValueOption("concurrentmap", + "concurrent map implementation for concurrent parameterized memoization") + .acceptAnyValue() + .defaultValue("ConcurrentHashMap") + .templateVariable("ConcurrentMap"); + Collection<String> filenames = new LinkedList<String>(); Option<Boolean> emptyContainerSingletons = new FlagOption("emptyContainerSingletons", @@ -477,6 +493,9 @@ public class Configuration { // New since 2.2.4: allOptions.add(emptyContainerSingletons); + allOptions.add(concurrentOption); + allOptions.add(numThreadsOption); + allOptions.add(concurrentMap); // Deprecated in 2.1.5. allOptions.add(doxygenOption); @@ -593,7 +612,6 @@ public class Configuration { tt.bind("CacheCycle", cacheCycle()); tt.bind("StaticState", staticState()); tt.bind("LazyMaps", lazyMaps()); - return root; } @@ -1303,4 +1321,9 @@ public class Configuration { public boolean emptyContainerSingletons() { return emptyContainerSingletons.value(); } + + /** @return {@code true} if concurrent evaluation is enabled. */ + public boolean concurrentEval() { + return concurrentOption.value(); + } } diff --git a/src/java/org/jastadd/JastAddTask.java b/src/java/org/jastadd/JastAddTask.java index cf06e5340a24bbe52c065495073014fa54fb1e22..54bcf165edf94b52911e80d2ee5f151de0f867f9 100644 --- a/src/java/org/jastadd/JastAddTask.java +++ b/src/java/org/jastadd/JastAddTask.java @@ -114,6 +114,14 @@ public class JastAddTask extends Task { setOption(config.beaverOption, enable); } + public void setConcurrent(boolean enable) { + setOption(config.concurrentOption, enable); + } + + public void setnum_threads(int num) { + setOption(config.numThreadsOption, "" + num); + } + public void setLineColumnNumbers(boolean enable) { setOption(config.lineColumnNumbersOption, enable); } diff --git a/src/template/ast/ASTNode.tt b/src/template/ast/ASTNode.tt index 9e3ded2dfa4405bdb882382baec535e0c51cdfe8..5d7cf6a3d99a571be52f7b3c34a1dc58373f0080 100644 --- a/src/template/ast/ASTNode.tt +++ b/src/template/ast/ASTNode.tt @@ -73,9 +73,6 @@ $endif /** @apilevel internal */ public static final boolean $ASTNode.generatedWithCacheCycle = $CacheCycle; - /** @apilevel internal */ - public static final boolean $ASTNode.generatedWithComponentCheck = $ComponentCheck; - $if(!JJTree) /** @apilevel low-level */ protected $ASTNode $ASTNode.parent; @@ -90,7 +87,39 @@ $if(TracingEnabled) } $endif -$if(StaticState) +$if(Concurrent) + /** @apilevel internal */ + private static ThreadLocal<$StateClass> $ASTNode.state = new ThreadLocal<$StateClass>() { + @Override + public $StateClass initialValue() { + $StateClass state = new $StateClass(); + threadStates.add(state); + return state; + } + }; + + public static java.util.Queue<$StateClass> $ASTNode.threadStates = + new java.util.concurrent.ConcurrentLinkedQueue<$StateClass>(); + + + /** @apilevel internal */ + public final static $StateClass $ASTNode.state() { + return state.get(); + } + + /** @apilevel internal */ + public final static void $ASTNode.resetState() { + state = new ThreadLocal<$StateClass>() { + @Override + public $StateClass initialValue() { + $StateClass state = new $StateClass(); + threadStates.add(state); + return state; + } + }; + } +$else + $if(StaticState) /** @apilevel internal */ private static $StateClass $ASTNode.state = new $StateClass(); @@ -98,10 +127,20 @@ $if(StaticState) public final $StateClass $ASTNode.state() { return state; } -$else + + /** @apilevel internal */ + public final static $StateClass $ASTNode.resetState() { + return state = new $StateClass(); + } + $else /** @apilevel internal */ private $StateClass $ASTNode.state = null; + /** @apilevel internal */ + public final void $ASTNode.resetState() { + state = null; + } + /** @apilevel internal */ public final $StateClass $ASTNode.state() { if (state == null) { @@ -114,6 +153,7 @@ $else } return state; } + $endif $endif $if(LegacyRewrite) diff --git a/src/template/ast/Collections.tt b/src/template/ast/Collections.tt index 4e74457f549b1bcb339f970181ff2d24e0703203..660efeced6f71c5cbd3eae21475fb79d9b1287bb 100644 --- a/src/template/ast/Collections.tt +++ b/src/template/ast/Collections.tt @@ -62,15 +62,19 @@ CollDecl.computeMethod:twoPhase [[ #rootType root = (#rootType) node; root.survey_#collectionId(); #getType _computedValue = $BottomValue; - if (root.contributorMap_#collectionId.containsKey(this)) { - for ($ASTNode contributor : root.contributorMap_#collectionId.get(this)) { - contributor.contributeTo_#(signature)(_computedValue); - } - } + $include(CollDecl.collectContributions) return _computedValue; } ]] +CollDecl.collectContributions [[ +if (root.contributorMap_#collectionId.containsKey(this)) { + for ($ASTNode contributor : root.contributorMap_#collectionId.get(this)) { + contributor.contributeTo_#(signature)(_computedValue); + } +} +]] + CollEq.collectContributors:onePhase [[ // #declaredat $if(HasCondition) diff --git a/src/template/ast/CopyNode.tt b/src/template/ast/CopyNode.tt index be2bfa2a66cda1c7a98c66f1d969cc5df9126002..6581a4cfd044ebcf0f60d92684c286a4df63dd73 100644 --- a/src/template/ast/CopyNode.tt +++ b/src/template/ast/CopyNode.tt @@ -46,6 +46,9 @@ $if(IncrementalEnabled) node.init_children = null; node.children_computed = null; inc_copyHandlers(node); +$endif +$if(Concurrent) + node.makeFreshNode(); $endif return node; } catch (CloneNotSupportedException e) { diff --git a/src/template/ast/State.tt b/src/template/ast/State.tt index 8a3cbc4a677a22939d410ce7c2c27dfb04c261f4..3f859edf4bfdd41814469037027ad854fb9f6173 100644 --- a/src/template/ast/State.tt +++ b/src/template/ast/State.tt @@ -26,28 +26,100 @@ # POSSIBILITY OF SUCH DAMAGE. ASTState = [[ +/** Wrapper class for storing nullable attribute values. */ +public class AttributeValue<T> { + /** + * This singleton object is an illegal, unused, attribute value. + * It represents that an attribute has not been memoized, or that + * a circular attribute approximation has not been initialized. + */ + public static final Object NONE = new Object(); + + public final T value; + + public AttributeValue(T value) { + this.value = value; + } + + public static <V> boolean equals(AttributeValue<V> v1, AttributeValue<V> v2) { + if (v1 == null || v2 == null) { + return v1 == v2; + } else { + return equals(v1.value, v2.value); + } + } + + public static <V> boolean equals(V v1, V v2) { + if (v1 == null || v2 == null) { + return v1 == v2; + } else { + return v1 == v2 || v1.equals(v2); + } + } +} + +$if(Concurrent) +/** + * Tuple for storing attribute value and done flag. + * <p>Used in concurrent circular attribute evaluation. + */ +public class CircularAttributeValue { + public volatile boolean done; + public final AtomicReference value; + + public CircularAttributeValue() { + this.value = new AtomicReference(AttributeValue.NONE); + } + + public CircularAttributeValue(Object value) { + this.value = new AtomicReference(value); + } + + public boolean compareAndSet(Object expected, Object next) { + return value.compareAndSet(expected, next); + } + + public Object get() { + return value.get(); + } +} + +$endif /** @apilevel internal */ public class $StateClass { - /** @apilevel internal */ + /** + * This class stores an attribute value tagged with an iteration ID for + * a circular evaluation. + * + * @apilevel internal + */ protected static class CircularValue { Object value; Cycle cycle; } /** - * Instances of this class are used to uniquely identify circular evaluation cycles. + * Instances of this class are used to uniquely identify circular evaluation iterations. + * These iteration ID objects are created for each new fixed-point iteration in + * a circular evaluation. + * * @apilevel internal */ protected static class Cycle { } - /** The cycle ID used outside of circular evaluation. */ + /** + * The iteration ID used outside of circular evaluation. + * + * <p>This is the iteration ID when no circular evaluation is ongoing. + */ public static final Cycle NON_CYCLE = new Cycle(); /** * Tracks the state of the current circular evaluation. This class defines a * stack structure where the next element on the stack is pointed to by the * {@code next} field. + * * @apilevel internal */ protected static class CircleState { @@ -71,11 +143,47 @@ $endif /** Cycle ID of the latest cycle in this circular evaluation. */ Cycle cycle = NON_CYCLE; +$if(Concurrent) + /** + * Tracks attribute value observations during circular evaluation. + * + * <p>The purpose of this map is a little different when evaluating + * a circular attribute and when evaluating a non-circular attribute + * during a fixed-point evaluation. + * For a circularly evaluated attribute, this map is only used to track + * iteration IDs. For a non-circularly evaluated attribute, the map + * stores thread-local attribute approximations. + */ + final java.util.Map<Object, Observation> observations; +$endif + protected CircleState(CircleState next) { this.next = next; +$if(Concurrent) + if (next != null) { + observations = new java.util.IdentityHashMap<Object, Observation>(); + } else { + // The bottom observation map is immutable. + // This makes it easier to detect if something breaks the rule of + // not storing observations in the bottom state. + observations = java.util.Collections.emptyMap(); + } +$endif } } +$if(Concurrent) + class Observation { + public final Object value; + public final Cycle cycle; + + public Observation(Object value, Cycle cycle) { + this.value = value; + this.cycle = cycle; + } + } +$endif + /** Sentinel circle state representing non-circular evaluation. */ private static final CircleState CIRCLE_BOTTOM = new CircleState(null); @@ -111,6 +219,84 @@ $endif circle = next; } +$if(Concurrent) + /** + * Store an attribute value and iteration index for an attribute. + * + * <p>This is used by memoized attributes during circular evaluation to store the thread-local + * approximation tagged with the current iteration ID. + */ + public void observe(Object attributeId, Object value) { + circle.observations.put(attributeId, new Observation(value, circle.cycle)); + } + + /** + * Update iteration ID for circular attribute. + * + * <p>Because circular attributes don't use the observed value, the observation value is + * set to NONE. + */ + public void updateIteration(Object attributeId) { + circle.observations.put(attributeId, new Observation(AttributeValue.NONE, circle.cycle)); + } + + /** + * Check if the stored iteration ID for the attribute is equal to the + * current iteration id. + * + * @return {@code true} if the attribute was observed on the current cycle. + */ + public boolean observedInCycle(Object attributeId) { + Observation observation = circle.observations.get(attributeId); + return observation != null && observation.cycle == circle.cycle; + } + + /** + * Used by non-circularly evaluated attributes during fixed-point computation + * to get the most recent approximation for the attribute. + * + * @return the last observed attribute value for the given attribute. + */ + public <T> T lastObservedValue(Object attributeId) { + Observation observation = circle.observations.get(attributeId); + return observation == null ? null : (T) observation.value; + } +$endif + + /** + * Maps circular attribute to last evaluated cycle index. + * @apilevel internal + */ + private java.util.Map<Object, Integer> visited = new java.util.IdentityHashMap<Object, Integer>(); + + /** + * Check if attribute was already visited during the current cycle. + * @apilevel internal + * @return {@code true} if the attribute was already visited. + */ + protected boolean checkAndSetVisited(Object attribute, int cycle) { + boolean result = visited.containsKey(attribute) && visited.get(attribute) == cycle; + visited.put(attribute, cycle); + return result; + } + + /** + * Reset visited cycle tracking for this thread. + * @apilevel internal + */ + protected void clearVisited() { + visited.clear(); + } + + // TODO(joqvist): may not be necessary. + /** + * Reset visit tracker for a single attribute. + * @apilevel internal + */ + protected void resetVisited(Object attribute) { + visited.remove(attribute); + } + /** @apilevel internal */ protected void leaveCircle() { $if(ComponentCheck) @@ -241,18 +427,96 @@ $endif $include(State.incHook) $if(TracingEnabled) + public interface ReceiverFactory { + Trace.Receiver build(); + } + + public static ReceiverFactory receiverFactory = new ReceiverFactory() { + public Trace.Receiver build() { + return new Trace.Receiver() { + public void accept($StateClass.Trace.Event event, $ASTNode node, String attribute, + Object params, Object value) { + } + }; + } + }; + private Trace trace = null; /** @return the tracer instance used for tracing attribute evaluation in this AST. */ public Trace trace() { if (trace == null) { - trace = new Trace(); + trace = new Trace(receiverFactory.build()); } return trace; } $include(TraceClass) $endif +$if(Concurrent) + public static int NUM_THREADS = $NumThreads; + + /** State-global thread pool. */ + private java.util.concurrent.ExecutorService threadPool = null; + + public synchronized java.util.concurrent.ExecutorService threadPool() { + if (threadPool != null) { + return threadPool; + } else { + threadPool = newDaemonThreadPool(NUM_THREADS); + return threadPool; + } + } + + /** + * Worker threads will shut down when the JVM stops, but this method can be called + * to force them to stop prematurely. + */ + public synchronized void stopWorkers() { + threadPool.shutdown(); + threadPool = null; + } + /** Global thread pool. */ + private static java.util.concurrent.ExecutorService globalThreadPool = null; + + public static java.util.concurrent.ExecutorService globalThreadPool() { + synchronized ($StateClass.class) { + if (globalThreadPool != null) { + return globalThreadPool; + } else { + globalThreadPool = newDaemonThreadPool(NUM_THREADS); + return globalThreadPool; + } + } + } + + /** + * Starts a thread pool with worker threads. The .shutdown() method should + * be called once the work is finished, otherwise the worker threads prevent + * Java VM from shutting down. + */ + public static java.util.concurrent.ExecutorService newThreadPool(int numThreads) { + return java.util.concurrent.Executors.newFixedThreadPool(numThreads); + } + + /** + * Starts a thread pool with daemon worker threads. Daemon threads do not + * prevent the Java VM from shutting down. + */ + public static java.util.concurrent.ExecutorService newDaemonThreadPool(int numThreads) { + final java.util.concurrent.ThreadFactory threadFactory = + java.util.concurrent.Executors.defaultThreadFactory(); + return java.util.concurrent.Executors.newFixedThreadPool(numThreads, + new java.util.concurrent.ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = threadFactory.newThread(r); + thread.setDaemon(true); + return thread; + } + }); + } +$endif } ]] diff --git a/src/template/concurrent/Attributes.tt b/src/template/concurrent/Attributes.tt new file mode 100644 index 0000000000000000000000000000000000000000..95659a78f58d3b721e28a086445ca4cf727ffbb1 --- /dev/null +++ b/src/template/concurrent/Attributes.tt @@ -0,0 +1,346 @@ +# Copyright (c) 2013-2017, The JastAdd Team +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the Lund University nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +AttrDecl.returnStmt [[ +$if(#isAttrNTA) +#(getType) node = (#boxedType) this.getChild(#(signature)ChildPosition()); +$include(AttrDecl.incHookAttrCompEnd) +return node; +$else + $if(!#isCircular) +$include(AttrDecl.incHookAttrCompEnd) + $endif +return _result; +$endif +]] + +AttrDecl.cacheDeclarations [[ +$if(#isParameterized) + $if(#declaredNTA) + /** @apilevel internal */ + protected $List<#getType> #(signature)_list; + { + #(signature)_list = new $List<#getType>(); + #(signature)_list.setParent(this); + } + + $endif + $if(#isCircular) + /** @apilevel internal */ + protected ConcurrentMap<Object, CircularAttributeValue> #(signature)_values = + new $ConcurrentMap<Object, CircularAttributeValue>(); + $else + /** @apilevel internal */ + protected ConcurrentMap<Object, AtomicReference> #(signature)_values = new $ConcurrentMap<Object, AtomicReference>(); + $endif +$else + $if(#isCircular) + /** @apilevel internal */ + protected CircularAttributeValue #(signature)_value = new CircularAttributeValue(); + $else + $if(#isIdentityComparable) + /** @apilevel internal */ + protected #getType #(signature)_value; + + /** @apilevel internal */ + protected volatile boolean #(signature)_computed; + $if(#cacheInCycle) + + protected Object #(signature)_id = new Object(); + $endif + $else + /** @apilevel internal */ + protected AtomicReference<Object> #(signature)_value = new AtomicReference<Object>(AttributeValue.NONE); + $endif + $endif +$endif +]] + +AttrDecl.emitInlineComputeWithTry [[ +#docComment + #annotations + $include(AttrDecl.generatedAnnotations) + public #synchronizedModifier#getType #name($ParamDecl) { + #parameterStructure + $include(AttrDecl.cacheCheck) + $include(AttrDecl.enterLazyAttribute) + #lazyState + $include(AttrDecl.traceComputeBegin) + try $ComputeBody + finally { + $include(AttrDecl.leaveLazyAttribute) + #higherOrderAttributeCode + $include(AttrDecl.traceComputeEndInline) + } + } +]] + +AttrDecl.emitInlineComputeWithoutTry [[ +#docComment + #annotations + $include(AttrDecl.generatedAnnotations) + public #synchronizedModifier#getType #name($ParamDecl) { + #parameterStructure + #lazyState + $include(AttrDecl.cacheCheck) + $include(AttrDecl.traceComputeBegin) + $ComputeBody + } +]] + +AttrDecl.visitedDeclaration [[ +]] + +SynDecl.higherOrderAttributeCode:norewrite [[ +$if(#isParameterized) +_result.setParent(#(signature)_list); +$else +_result.setParent(this); +$endif +]] + +SynDecl.higherOrderAttributeCode:rewritesEnabled [[ +$if(#isParameterized) +_result.setParent(#(signature)_list); +$else +_result.setParent(this); +$endif +]] + +AttrDecl.emitEquation [[ +#docComment + #annotations + $include(AttrDecl.generatedAnnotations) + public #synchronizedModifier#getType #name($ParamDecl) { + #parameterStructure + #lazyState + $include(AttrDecl.cacheCheck) + $include(AttrDecl.incHookAttrCompStart) + $include(AttrDecl.cacheInit) + $include(AttrDecl.enterLazyAttribute) + $include(AttrDecl.traceComputeBegin) +$if(#isMemoized) + #getType _result = $ComputeRhs; + $if(!#isParameterized) + #boxedType _value = _result; + $endif +$else + #getType _result = $ComputeRhs; +$endif + $include(AttrDecl.traceComputeEnd) + #higherOrderAttributeCode + $include(AttrDecl.cacheUpdate) + $include(AttrDecl.leaveLazyAttribute) + $include(AttrDecl.returnStmt) + } +]] + +AttrDecl.cacheCheck [[ +$if(#isMemoized) +$include(AttrDecl.incHookAttrRead) +$if(#isPrimitive) + $if(#isParameterized) +AtomicReference _container = #(signature)_values.get(_parameters); +if (_container == null) { + AtomicReference _reg = new AtomicReference(AttributeValue.NONE); + _container = #(signature)_values.putIfAbsent(_parameters, _reg); + if (_container == null) { + _container = _reg; + } +} +if (_container.get() != AttributeValue.NONE) { + return (#boxedType) _container.get(); + $else +if (#(signature)_computed) { + return #(signature)_value; + $endif + $if(#cacheInCycle) +} else { + $if(#isParameterized) + if (state.observedInCycle(_container)) { + #boxedType _value = state.<#boxedType>lastObservedValue(_container); + $include(AttrDecl.traceCacheRead) + return _value; + } + $else + if (state.observedInCycle(#(signature)_id)) { + #boxedType _value = state.<#boxedType>lastObservedValue(#(signature)_id); + $include(AttrDecl.traceCacheRead) + return _value; + } + $endif + $endif +} +$else + $if(#isParameterized) +AtomicReference _container = #(signature)_values.get(_parameters); +if (_container == null) { + AtomicReference _reg = new AtomicReference(AttributeValue.NONE); + _container = #(signature)_values.putIfAbsent(_parameters, _reg); + if (_container == null) { + _container = _reg; + } +} +if (_container.get() != AttributeValue.NONE) { + return (#boxedType) _container.get(); + $else + $if(#isIdentityComparable) +if (#(signature)_computed) { + return #(signature)_value; + $else +Object cached_value = #(signature)_value.get(); +if (cached_value != AttributeValue.NONE) { + #boxedType _value = (#boxedType) cached_value; + $include(AttrDecl.traceCacheRead) + return _value; + $endif + $endif + $if(#cacheInCycle) +} else { + $if(#isParameterized) + if (state.observedInCycle(_container)) { + #boxedType _value = state.<#boxedType>lastObservedValue(_container); + $include(AttrDecl.traceCacheRead) + return _value; + } + $else + $if(#isIdentityComparable) + if (state.observedInCycle(#(signature)_id)) { + #boxedType _value = state.<#boxedType>lastObservedValue(#(signature)_id); + return _value; + } + $else + if (state.observedInCycle(#(signature)_value)) { + #boxedType _value = state.<#boxedType>lastObservedValue(#(signature)_value); + $include(AttrDecl.traceCacheRead) + return _value; + } + $endif + $endif + $endif +} +$endif +$endif +]] + +AttrDecl.cacheUpdate [[ +$if(#isMemoized) +$if(#isPrimitive) + $if(#simpleCacheCheck) + $if(#isParameterized) +_container.compareAndSet(AttributeValue.NONE, _result); + $else +#(signature)_value = _result; +#(signature)_computed = true; + $if(TraceCache) +state().trace().cacheWrite(this, "#hostClassName.#signatureJavaStyle", "", _result); + $endif + $endif + $else +if (state.inCircle()) { + $if(#cacheInCycle) + $if(#isParameterized) + state.observe(_container, _result); + $else + state.observe(#(signature)_id, _result); + $endif +} else { + $endif + $if(#isParameterized) + _container.compareAndSet(AttributeValue.NONE, _result); + $else + #(signature)_value = _result; + #(signature)_computed = true; + $if(TraceCache) + state().trace().cacheWrite(this, "#hostClassName.#signatureJavaStyle", "", _result); + $endif + $endif +} + $endif +$else + $if(#simpleCacheCheck) + $if(#isParameterized) +_container.compareAndSet(AttributeValue.NONE, _result); +_result = (#boxedType) _container.get(); + $else + $if(#isIdentityComparable) +#(signature)_value = _result; +#(signature)_computed = true; + $if(TraceCache) +state().trace().cacheWrite(this, "#hostClassName.#signatureJavaStyle", "", _result); + $endif + $else +if (!#(signature)_value.compareAndSet(AttributeValue.NONE, _value)) { + _result = (#boxedType) #(signature)_value.get(); + $if(TraceCache) + state().trace().cacheWrite(this, "-#hostClassName.#signatureJavaStyle", "", _result); +} else { + state().trace().cacheWrite(this, "#hostClassName.#signatureJavaStyle", "", _result); + $endif +} + $endif + $endif + $else +if (state.inCircle()) { + $if(#cacheInCycle) + $if(#isParameterized) + state.observe(_container, _result); + $else + $if(#isIdentityComparable) + state.observe(#(signature)_id, _result); + $else + state.observe(#(signature)_value, _result); + $endif + $endif +} else { + $endif + $if(#isParameterized) + _container.compareAndSet(AttributeValue.NONE, _result); + _result = (#boxedType) _container.get(); + $else + $if(#isIdentityComparable) + #(signature)_value = _result; + #(signature)_computed = true; + $if(TraceCache) + state().trace().cacheWrite(this, "#hostClassName.#signatureJavaStyle", "", _result); + $endif + $else + if (!#(signature)_value.compareAndSet(AttributeValue.NONE, _value)) { + _result = (#boxedType) #(signature)_value.get(); + $if(TraceCache) + state().trace().cacheWrite(this, "-#hostClassName.#signatureJavaStyle", "", _result); + } else { + state().trace().cacheWrite(this, "#hostClassName.#signatureJavaStyle", "", _result); + $endif + } + $endif + $endif +} + $endif +$endif +$endif +]] diff --git a/src/template/concurrent/Circular.tt b/src/template/concurrent/Circular.tt new file mode 100644 index 0000000000000000000000000000000000000000..bb5243f7d8ab0c774047ba4400decdfe45e8b4e8 --- /dev/null +++ b/src/template/concurrent/Circular.tt @@ -0,0 +1,263 @@ +# Copyright (c) 2013-2017, The JastAdd Team +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the Lund University nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +AttrDecl.cycleDeclaration [[ +]] + +AttrDecl.circularEquation:unparameterized [[ + #annotations + $include(AttrDecl.generatedAnnotations) + public #getType #name() { + if (#(signature)_value.done) { + return (#boxedType) #(signature)_value.get(); + } + Object _previous = #(signature)_value.get(); + if (_previous == AttributeValue.NONE) { +$if(#isCollection) + $ASTNode _node = this; + while (_node != null && !(_node instanceof #rootType)) { + _node = _node.getParent(); + } + $include(CollDecl.collDebugCheck) + #rootType root = (#rootType) _node; + if (root.collecting_contributors_#collectionId) { + throw new RuntimeException("Circularity during survey phase"); + } + root.survey_#collectionId(); +$endif + #boxedType _bottom = $BottomValue; +$if(#getNTA) + if (_bottom != null) { + _bottom.setParent(#ntaParent); + } +$endif + if (!#(signature)_value.compareAndSet(AttributeValue.NONE, _bottom)) { + _bottom = (#boxedType) #(signature)_value.get(); + } + _previous = _bottom; + $StateClass state = state(); + } + $StateClass state = state(); + Object _id = #(signature)_value; + if (!state.inCircle() || state.calledByLazyAttribute()) { + $include(AttrDecl.traceCircularEnterCase1) + state.enterCircle(); + #tracePrintCycleBeginString + do { + state.nextCycle(); + state.updateIteration(_id); + #tracePrintBeginComputingValue + $include(AttrDecl.incHookAttrCompStartCircular) + $include(AttrDecl.traceComputeBegin) + #boxedType _next = $CircularComputeRhs; + $include(AttrDecl.traceComputeEnd) + $include(AttrDecl.incHookAttrCompEndCircular) +$if(#isCircularNta) + if (_previous != _next || _next.canRewrite()) { +$else + if (!AttributeValue.equals(_previous, _next)) { +$endif + state.setChangeInCycle(); + $include(AttrDecl.traceCircularCase1Change) + } +$if(#getNTA) + if (_next != null) { + _next.setParent(#ntaParent); + } +$endif + // Always update the approximation, so that the !canRewrite() NTA case is handled correctly. + if (!#(signature)_value.compareAndSet(_previous, _next)) { + _next = (#boxedType) #(signature)_value.get(); + } + _previous = _next; + #tracePrintStartingCycle + #cycleLimitCheck + } while (state.testAndClearChangeInCycle()); + $TracePrintReturnNewValue + #tracePrintCycleEndString + $include(AttrDecl.traceCircularExitCase1) + state.leaveCircle(); + #(signature)_value.done = true; + return (#boxedType) _previous; + } else if (!state.observedInCycle(_id)) { + $include(AttrDecl.traceCircularEnterCase2) + state.updateIteration(_id); + #tracePrintBeginComputingValue + $include(AttrDecl.incHookAttrCompStartCircular) + $include(AttrDecl.traceComputeBegin) + #boxedType _next = $CircularComputeRhs; + $include(AttrDecl.traceComputeEnd) + $include(AttrDecl.incHookAttrCompEndCircular) +$if(#isCircularNta) + if (_previous != _next || _next.canRewrite()) { +$else + if (!AttributeValue.equals(_previous, _next)) { +$endif + state.setChangeInCycle(); + $include(AttrDecl.traceCircularCase2Change) + } + // Always update the approximation, so that the !canRewrite() NTA case is handled correctly. +$if(#getNTA) + if (_next != null) { + _next.setParent(#ntaParent); + } +$endif + if (!#(signature)_value.compareAndSet(_previous, _next)) { + _next = (#boxedType) #(signature)_value.get(); + } + state.updateIteration(_id); + $TracePrintReturnNewValue + $include(AttrDecl.traceCircularExitCase2) + return _next; + } else { + $TracePrintReturnPreviousValue + $include(AttrDecl.traceCircularExitCase3) + return (#boxedType) _previous; + } + } +]] + +AttrDecl.circularEquation:parameterized [[ + #annotations + $include(AttrDecl.generatedAnnotations) + public #getType #name($ParamDecl) { + #parameterStructure + CircularAttributeValue _value; + #boxedType _previous; + if (#(signature)_values.containsKey(_parameters)) { + _value = #(signature)_values.get(_parameters); + _previous = (#boxedType) _value.get(); + if (_value.done) { + return _previous; + } + } else { + #getType _bottom = $BottomValue; +$if(#getNTA) + if (_bottom != null) { + if (#(signature)_list.get() == null) { + $List _list _list = new $List(); + _list.setParent(#ntaParent); + #(signature)_list.compareAndSet(null, _list); + } + (($ASTNode) _bottom).setParent(#(signature)_list.get()); + } +$endif + _value = new CircularAttributeValue(_bottom); + #(signature)_values.putIfAbsent(_parameters, _value); + _value = #(signature)_values.get(_parameters); + _previous = (#boxedType) _value.get(); + $StateClass state = state(); + } + $StateClass state = state(); + Object _id = _value; + #getType _result; + if (!state.inCircle() || state.calledByLazyAttribute()) { + $include(AttrDecl.traceCircularEnterCase1) + state.enterCircle(); + #tracePrintCycleBeginString + do { + state.nextCycle(); + state.updateIteration(_id); + #tracePrintBeginComputingValue + $include(AttrDecl.incHookAttrCompStartCircular) + $include(AttrDecl.traceComputeBegin) + #boxedType _next = $CircularComputeRhs; + $include(AttrDecl.traceComputeEnd) + $include(AttrDecl.incHookAttrCompEndCircular) +$if(#isCircularNta) + if (_previous != _next || _next.canRewrite()) { +$else + if (!AttributeValue.equals(_previous, _next)) { +$endif + state.setChangeInCycle(); + $include(AttrDecl.traceCircularCase1Change) + } + // Always update the approximation, so that the !canRewrite() NTA case is handled correctly. +$if(#getNTA) + if (_next != null) { + if (#(signature)_list.get() == null) { + $List _list _list = new $List(); + _list.setParent(#ntaParent); + #(signature)_list.compareAndSet(null, _list); + } + (($ASTNode) _next).setParent(#(signature)_list.get()); + } +$endif + if (!_value.compareAndSet(_previous, _next)) { + _previous = (#boxedType) _value.get(); + } + #tracePrintStartingCycle + #cycleLimitCheck + } while (state.testAndClearChangeInCycle()); + $TracePrintReturnNewValue + #tracePrintCycleEndString + $include(AttrDecl.traceCircularExitCase1) + state.leaveCircle(); + _value.done = true; + return _previous; + } else if (!state.observedInCycle(_id)) { + $include(AttrDecl.traceCircularEnterCase2) + state.updateIteration(_id); + #tracePrintBeginComputingValue + $include(AttrDecl.incHookAttrCompStartCircular) + $include(AttrDecl.traceComputeBegin) + #boxedType _next = $CircularComputeRhs; + $include(AttrDecl.traceComputeEnd) + $include(AttrDecl.incHookAttrCompEndCircular) +$if(#isCircularNta) + if (_previous != _next || _next.canRewrite()) { +$else + if (!AttributeValue.equals(_previous, _next)) { +$endif + state.setChangeInCycle(); + $include(AttrDecl.traceCircularCase2Change) + } + // Always update the approximation, so that the !canRewrite() NTA case is handled correctly. +$if(#getNTA) + if (_next != null) { + if (#(signature)_list.get() == null) { + $List _list _list = new $List(); + _list.setParent(#ntaParent); + #(signature)_list.compareAndSet(null, _list); + } + (($ASTNode) _next).setParent(#(signature)_list.get()); + } +$endif + if (!_value.compareAndSet(_previous, _next)) { + _next = (#boxedType) _value.get(); + } + state.updateIteration(_id); + $TracePrintReturnNewValue + $include(AttrDecl.traceCircularExitCase2) + return _next; + } else { + $TracePrintReturnPreviousValue + $include(AttrDecl.traceCircularExitCase3) + return (#boxedType) _previous; + } + } +]] diff --git a/src/template/concurrent/Collections.tt b/src/template/concurrent/Collections.tt new file mode 100644 index 0000000000000000000000000000000000000000..daa7bc47a9d9cf3394f2f6e8902859a119489af1 --- /dev/null +++ b/src/template/concurrent/Collections.tt @@ -0,0 +1,198 @@ +# Copyright (c) 2013-2017, The JastAdd Team +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the Lund University nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +CollDecl.collectContributions [[ +$if(#parallel) +final java.util.Set<$ASTNode> _contributorSet = root.contributorMap_#collectionId.get(this); +final ArrayList<$ASTNode> _contributors = new ArrayList<$ASTNode>(); +if (_contributorSet != null) { + _contributors.addAll(_contributorSet); +} +final AtomicInteger nextContributor = new AtomicInteger(0); +java.util.concurrent.ExecutorService threadPool = state().threadPool(); +Collection<Future<LinkedList<#componentType>>> futures = + new LinkedList<Future<LinkedList<#componentType>>>(); +for (int i = 0; i < $StateClass.NUM_THREADS; ++i) { + futures.add(threadPool.submit(new Callable<LinkedList<#componentType>>() { + @Override + public LinkedList<#componentType> call() { + LinkedList<#componentType> value = new LinkedList<#componentType>(); + while (true) { + int next = nextContributor.getAndIncrement(); + if (next >= _contributors.size()) { + return value; + } + $ASTNode contributor = _contributors.get(next); + contributor.contributeTo_#(signature)(value); + } + } + })); +} +for (Future<LinkedList<#componentType>> future : futures) { + try { + _computedValue.addAll(future.get()); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } +} +$else +if (root.contributorMap_#collectionId.containsKey(this)) { + for ($ASTNode contributor : root.contributorMap_#collectionId.get(this)) { + contributor.contributeTo_#(signature)(_computedValue); + } +} +$endif +]] + +CollDecl.contributeTo:default [[ +$if(#parallel) + protected void contributeTo_#(signature)(java.util.Collection<#componentType> collection) { + } +$else + protected void contributeTo_#(signature)(#getType collection) { + } +$endif +]] + +CollDecl.contributeTo:header [[ +$if(#parallel) + protected void contributeTo_#(signature)(java.util.Collection<#componentType> collection) { +$if(!IsAstNode) + super.contributeTo_#(signature)(collection); +$endif +$else + protected void contributeTo_#(signature)(#getType collection) { +$if(!IsAstNode) + super.contributeTo_#(signature)(collection); +$endif +$endif +]] + +# The method to start the survey phase (collecting contributors). +CollDecl.surveyMethod [[ +$if(#onePhase) +private boolean collect_contributors_#collectionId = false; +$else +protected java.util.Map<$ASTNode, java.util.Set<$ASTNode>> contributorMap_#collectionId = null; +$if(Concurrent) +private Object surveyLock_#collectionId = new Object(); +$endif +$endif + +$if(#isCircular) + public boolean collecting_contributors_#collectionId = false; + +$endif + protected void survey_#collectionId() { + synchronized (surveyLock_#collectionId) { +$if(#onePhase) + if (!collect_contributors_#collectionId) { + collect_contributors_#collectionId = true; +$else + if (contributorMap_#collectionId == null) { + contributorMap_#collectionId = new java.util.IdentityHashMap<$ASTNode, java.util.Set<$ASTNode>>(); +$endif +$if(#isCircular) + collecting_contributors_#collectionId = true; +$endif +$if(#onePhase) + collect_contributors_#collectionId(this); +$else + $if(#parallelSurvey) + final #rootType root = this; + final java.util.Queue<ASTNode> queue = + new java.util.concurrent.ConcurrentLinkedQueue<ASTNode>(); + queue.add(this); + java.util.concurrent.ExecutorService threadPool = state().threadPool(); + LinkedList<Future<Map<$ASTNode, java.util.Set<$ASTNode>>>> futures = + new LinkedList<Future<Map<$ASTNode, java.util.Set<$ASTNode>>>>(); + for (int i = 0; i < $StateClass.NUM_THREADS; ++i) { + futures.add(threadPool.submit(new Callable<Map<$ASTNode, java.util.Set<$ASTNode>>>() { + @Override + public Map<$ASTNode, java.util.Set<$ASTNode>> call() { + Map<$ASTNode, java.util.Set<$ASTNode>> result = + new java.util.IdentityHashMap<$ASTNode, java.util.Set<$ASTNode>>(); + $ASTNode next = queue.poll(); + while (next != null) { + next.collect_contributors_#collectionId(root, result); + if (next.getNumChild() > 0) { + for (int i = 1; i < next.getNumChild(); ++i) { + $ASTNode child = next.getChild(i); + if (child != null) { + queue.add(child); + } + } + next = next.getChild(0); + if (next == null) { + next = queue.poll(); + } + } else { + next = queue.poll(); + } + } + return result; + } + })); + } + for (Future<Map<$ASTNode, java.util.Set<$ASTNode>>> future : futures) { + try { + for (Map.Entry<$ASTNode, java.util.Set<$ASTNode>> entry : future.get().entrySet()) { + java.util.Set<$ASTNode> contributors = contributorMap_#collectionId.get(entry.getKey()); + if (contributors == null) { + contributors = new java.util.HashSet<$ASTNode>(); + contributorMap_#collectionId.put(entry.getKey(), contributors); + } + contributors.addAll(entry.getValue()); + } + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + $else + collect_contributors_#collectionId(this, contributorMap_#collectionId); + $endif +$endif +$if(#isCircular) + collecting_contributors_#collectionId = false; +$endif + } + } + } +]] + +CollDecl.collectContributors:end [[ +$if(#onePhase) + super.collect_contributors_#collectionId(_root); +$else + super.collect_contributors_#collectionId(_root, _map); +$endif + } +]] diff --git a/src/template/concurrent/Flush.tt b/src/template/concurrent/Flush.tt new file mode 100644 index 0000000000000000000000000000000000000000..0195ea225c96fefaf82409d77ba28750b8b18429 --- /dev/null +++ b/src/template/concurrent/Flush.tt @@ -0,0 +1,82 @@ +# Copyright (c) 2013-2017, The JastAdd Team +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the Lund University nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +AttrDecl.resetAttrCache [[]] + +AttrDecl.resetAttrVisit [[]] + +ASTDecl.flushAttrCacheMethod [[ +$if(FlushAttr) + /** @apilevel internal */ + public void #name.flushAttrCache() { + // Flushing is not needed. Caches cleared when cloning. + } +$endif + /** @apilevel internal */ + public void #name.makeFreshNode() { + $if(!#isASTNodeDecl) + super.makeFreshNode(); + $endif + $MakeFreshNodeBody + } +]] + +# Reset all attribute caches. +AttrDecl.resetMethod [[ + /** @apilevel internal */ + private void #(signature)_reset() { + // Handled by creating a fresh copy of this node! + } + + /** @apilevel internal */ + private void #(signature)_fresh() { +$if(#isParameterized) + $if(#declaredNTA) + #(signature)_list = new $List<#getType>(); + #(signature)_list.setParent(this); + $endif + $if(#isCircular) + #(signature)_values = new $ConcurrentMap<Object, CircularAttributeValue>(); + $else + #(signature)_values = new $ConcurrentMap<Object, AtomicReference>(); + $endif +$else + $if(#isCircular) + #(signature)_value = new CircularAttributeValue(); + $else + $if(#isIdentityComparable) + #(signature)_computed = false; + $if(#cacheInCycle) + #(signature)_id = new Object(); + $endif + $else + #(signature)_value = new AtomicReference<Object>(AttributeValue.NONE); + $endif + $endif +$endif + } +]] diff --git a/src/template/concurrent/InheritedAttributes.tt b/src/template/concurrent/InheritedAttributes.tt new file mode 100644 index 0000000000000000000000000000000000000000..4b831ae85ffe5907f5d58dcca86c66f394132398 --- /dev/null +++ b/src/template/concurrent/InheritedAttributes.tt @@ -0,0 +1,65 @@ +# Copyright (c) 2013-2017, The JastAdd Team +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the Lund University nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +InhEq.emitNTAClause [[ +$if(IsParameterized) +if (_callerNode == $(AttrSignature)_list) { + // #declaredat + $EvalStmt +} +$else +if (_callerNode == #(childName)_value.get()) { + // #declaredat + $EvalStmt +} +$endif +]] + +# List child equation clause. +InhEq.emitListClause [[ +if (_callerNode == get#(childName)ListNoTransform()) { + // #declaredat + int $ChildIndexVar = _callerNode.getIndexOfChild(_childNode); + $EvalStmt +} +]] + +# Optional child equation clause. +InhEq.emitOptClause [[ +if (_callerNode == get#(childName)OptNoTransform()) { + // #declaredat + $EvalStmt +} +]] + +# Regular child equation clause. +InhEq.emitChildClause [[ +if (_callerNode == get#(childName)()) { + // #declaredat + $EvalStmt +} +]] diff --git a/src/template/concurrent/Trace.tt b/src/template/concurrent/Trace.tt new file mode 100644 index 0000000000000000000000000000000000000000..eb9646e0dd5061ec6e4bf958e0610f0183200af5 --- /dev/null +++ b/src/template/concurrent/Trace.tt @@ -0,0 +1,169 @@ + +AttrDecl.traceComputeEnd [[ +$if (TraceCompute) + $if(#isParameterized) + $if(#isCircular) +state().trace().computeEnd(this, "#hostClassName.#signatureJavaStyle", _parameters, _previous); + $else +state().trace().computeEnd(this, "#hostClassName.#signatureJavaStyle", _parameters, _result); + $endif + $else + $if(#isCircular) +state().trace().computeEnd(this, "#hostClassName.#signatureJavaStyle", "", _previous); + $else +state().trace().computeEnd(this, "#hostClassName.#signatureJavaStyle", "", _result); + $endif + $endif +$endif +]] + +AttrDecl.traceCircularCase1Change [[ +$if (TraceCircular) + $if(#getNTA) + $if(#isParameterized) +state().trace().circularNTACase1Change(this, "#hostClassName.#signatureJavaStyle", _parameters, + _previous.value + "->" + _next); + $else +state().trace().circularNTACase1Change(this, "#hostClassName.#signatureJavaStyle", "", + _previous.value + "->" + _next); + $endif + $else + $if(#isParameterized) +state().trace().circularCase1Change(this, "#hostClassName.#signatureJavaStyle", _parameters, + _previous.value + "->" + _next); + $else +state().trace().circularCase1Change(this, "#hostClassName.#signatureJavaStyle", "", + _previous.value + "->" + _next); + $endif + $endif +$endif +]] + +AttrDecl.traceCircularCase2Change [[ +$if (TraceCircular) + $if(#getNTA) + $if(#isParameterized) +state().trace().circularNTACase2Change(this, "#hostClassName.#signatureJavaStyle", _parameters, + _previous.value + "->" + _next); + $else +state().trace().circularNTACase2Change(this, "#hostClassName.#signatureJavaStyle", "", + _previous.value + "->" + _next); + $endif + $else + $if(#isParameterized) +state().trace().circularCase2Change(this, "#hostClassName.#signatureJavaStyle", _parameters, + _previous.value + "->" + _next); + $else +state().trace().circularCase2Change(this, "#hostClassName.#signatureJavaStyle", "", + _previous.value + "->" + _next); + $endif + $endif +$endif +]] + +AttrDecl.traceCircularExitCase2 [[ +$if (TraceCircular) + $if(#getNTA) + $if(#isParameterized) +state().trace().exitCircularNTACase2(this, "#hostClassName.#signatureJavaStyle", _parameters, _previous); + $else +state().trace().exitCircularNTACase2(this, "#hostClassName.#signatureJavaStyle", "", _previous); + $endif + $else + $if(#isParameterized) +state().trace().exitCircularCase2(this, "#hostClassName.#signatureJavaStyle", _parameters, _previous); + $else +state().trace().exitCircularCase2(this, "#hostClassName.#signatureJavaStyle", "", _previous); + $endif + $endif +$endif +]] + +AttrDecl.traceCircularExitCase3 [[ +$if (TraceCircular) + $if(#getNTA) + $if(#isParameterized) +state().trace().exitCircularNTACase3(this, "#hostClassName.#signatureJavaStyle", _parameters, _previous); + $else +state().trace().exitCircularNTACase3(this, "#hostClassName.#signatureJavaStyle", "", _previous); + $endif + $else + $if(#isParameterized) +state().trace().exitCircularCase3(this, "#hostClassName.#signatureJavaStyle", _parameters, _previous); + $else +state().trace().exitCircularCase3(this, "#hostClassName.#signatureJavaStyle", "", _previous); + $endif + $endif +$endif +]] + +AttrDecl.traceCircularEnterCase2 [[ +$if (TraceCircular) + $if(#getNTA) + $if(#isParameterized) +state().trace().enterCircularNTACase2(this, "#hostClassName.#signatureJavaStyle", _parameters, #(signature)_values.get(_parameters).value); + $else +state().trace().enterCircularNTACase2(this, "#hostClassName.#signatureJavaStyle", "", #(signature)_value.get().value); + $endif + $else + $if(#isParameterized) +state().trace().enterCircularCase2(this, "#hostClassName.#signatureJavaStyle", _parameters, #(signature)_values.get(_parameters).value); + $else +state().trace().enterCircularCase2(this, "#hostClassName.#signatureJavaStyle", "", #(signature)_value.get().value); + $endif + $endif +$endif +]] + +AttrDecl.traceCircularEnterCase1 [[ +$if (TraceCircular) + $if(#getNTA) + $if(#isParameterized) +state().trace().enterCircularNTACase1(this, "#hostClassName.#signatureJavaStyle", _parameters, #(signature)_values.get(_parameters).value); + $else +state().trace().enterCircularNTACase1(this, "#hostClassName.#signatureJavaStyle", "", #(signature)_value.get().value); + $endif + $else + $if(#isParameterized) +state().trace().enterCircularCase1(this, "#hostClassName.#signatureJavaStyle", _parameters, #(signature)_values.get(_parameters).value); + $else +state().trace().enterCircularCase1(this, "#hostClassName.#signatureJavaStyle", "", #(signature)_value.get().value); + $endif + $endif +$endif +]] + +AttrDecl.traceCircularExitCase1 [[ +$if (TraceCircular) + $if(#getNTA) + $if(#isParameterized) +state().trace().exitCircularNTACase1(this, "#hostClassName.#signatureJavaStyle", _parameters, _previous); + $else +state().trace().exitCircularNTACase1(this, "#hostClassName.#signatureJavaStyle", "", _previous); + $endif + $else + $if(#isParameterized) +state().trace().exitCircularCase1(this, "#hostClassName.#signatureJavaStyle", _parameters, _previous); + $else +state().trace().exitCircularCase1(this, "#hostClassName.#signatureJavaStyle", "", _previous); + $endif + $endif +$endif +]] + +AttrDecl.traceCacheRead [[ +$if (TraceCache) + $if(#isMemoized) + $if(#isParameterized) + $if(#isCircular) +state().trace().cacheRead(this, "#hostClassName.#signatureJavaStyle", _parameters, _value); + $else +state().trace().cacheRead(this, "#hostClassName.#signatureJavaStyle", _parameters, #(signature)_values.get(_parameters)); + $endif + $else +state().trace().cacheRead(this, "#hostClassName.#signatureJavaStyle", "", _value); + $endif + $endif +$endif +]] + diff --git a/src/template/trace/Tracer.tt b/src/template/trace/Tracer.tt index 4c46c91cd66443270a211bc7bb34fc6722d1f140..cc565cd09de287ef3a01ad70f540a912fde207f3 100644 --- a/src/template/trace/Tracer.tt +++ b/src/template/trace/Tracer.tt @@ -90,6 +90,13 @@ public static class Trace { Object params, Object value); } + public Trace(Receiver receiver) { + this.receiver = receiver; + } + + public Trace() { + } + // The default event receiver does nothing. private Receiver receiver = new Receiver() { public void accept($StateClass.Trace.Event event, $ASTNode node, String attribute, @@ -105,6 +112,10 @@ public static class Trace { this.receiver = receiver; } + public Receiver getReceiver() { + return receiver; + } + /** * Trace that an attribute instance started its computation. * @param value The value of the attribute instance.