Skip to content
Snippets Groups Projects
Commit b15bdaa6 authored by Jesper's avatar Jesper
Browse files

Add concurrent attribute evaluation

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.

Changes in attribute evaluation:

* The Object.equals(Object) method is now used to compare NTA values for
  equality in fixed-point computations instead of the generated
  ASTNode.is$Equal() method.  This gives more flexibility for users to use
  their own equality tests.  It also means that is$Equal() can be removed.

Other changes:

* Added ASTNode.resetState() to reset the global AST state.
  This is needed in concurrent mode when multiple thread-local state
  instances can exist.
* Added constructor to the Trace class taking a trace receiver as argument.
parent d2ea60cc
No related branches found
No related tags found
No related merge requests found
Showing
with 1624 additions and 30 deletions
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> 2017-03-01 Jesper Öqvist <jesper.oqvist@cs.lth.se>
* Upgraded bootstrapping JastAdd version from 2.2.0 to 2.2.2. * Upgraded bootstrapping JastAdd version from 2.2.0 to 2.2.2.
......
...@@ -58,7 +58,9 @@ aspect Annotations { ...@@ -58,7 +58,9 @@ aspect Annotations {
|| a.isAnnotation("@LazyCondition") || a.isAnnotation("@LazyCondition")
|| a.isAnnotation("@Circular") || a.isAnnotation("@Circular")
|| a.isAnnotation("@CollectionGroup") || a.isAnnotation("@CollectionGroup")
|| a.isAnnotation("@Naive"); || a.isAnnotation("@Naive")
|| a.isAnnotation("@Parallel")
|| a.isAnnotation("@ParallelSurvey");
} }
/** /**
......
...@@ -83,6 +83,20 @@ aspect CollectionAttributes { ...@@ -83,6 +83,20 @@ aspect CollectionAttributes {
/** @return {@code true} if this contribution implicitly contributes to the collection root. */ /** @return {@code true} if this contribution implicitly contributes to the collection root. */
syn boolean CollEq.implicitTarget() = getReference().isEmpty(); 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. * Find interface collection attribute declarations.
*/ */
...@@ -171,15 +185,22 @@ aspect CollectionAttributes { ...@@ -171,15 +185,22 @@ aspect CollectionAttributes {
* *
* <p>Defaults to {@code true}. * <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 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. * One phase evaluation combines the survey and combination phases.
* *
* <p>Defaults to {@code false}. * <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(); syn boolean AttrEq.onePhase() = decl().onePhase();
/** /**
...@@ -341,6 +362,8 @@ aspect CollectionAttributes { ...@@ -341,6 +362,8 @@ aspect CollectionAttributes {
} }
if (skipSuperCall) { if (skipSuperCall) {
out.println(config().indent + "}"); out.println(config().indent + "}");
} else if (isASTNodeDecl() && !decl.parallelSurvey()) {
decl.templateContext().expand("CollDecl.collectContributors:default", out);
} else { } else {
if (isASTNodeDecl()) { if (isASTNodeDecl()) {
decl.templateContext().expand("CollDecl.collectContributors:default", out); decl.templateContext().expand("CollDecl.collectContributors:default", out);
......
...@@ -41,8 +41,15 @@ aspect Flush { ...@@ -41,8 +41,15 @@ aspect Flush {
for (AttrDecl attr : listOfCachedAttributes()) { for (AttrDecl attr : listOfCachedAttributes()) {
sb.append(attr.signature() + "_reset();\n"); sb.append(attr.signature() + "_reset();\n");
} }
tt.bind("FlushAttrCacheBody", sb.toString()); 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.flushAttrCacheMethod", out);
tt.expand("ASTDecl.flushCollectionCacheMethod", out); tt.expand("ASTDecl.flushCollectionCacheMethod", out);
} }
......
...@@ -50,6 +50,16 @@ aspect AttributeKind { ...@@ -50,6 +50,16 @@ aspect AttributeKind {
aspect JragCodeGen { 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 * Generate the declaredat documentation tag. If no file name is available
* then no tag is generated. * then no tag is generated.
...@@ -111,6 +121,21 @@ aspect JragCodeGen { ...@@ -111,6 +121,21 @@ aspect JragCodeGen {
public String Grammar.genImportsList() { public String Grammar.genImportsList() {
Set imports = new LinkedHashSet(); 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) { for (org.jastadd.jrag.AST.ASTCompilationUnit u : compilationUnits) {
imports.addAll(Unparser.getImports(u)); imports.addAll(Unparser.getImports(u));
} }
...@@ -299,6 +324,33 @@ aspect JragCodeGen { ...@@ -299,6 +324,33 @@ aspect JragCodeGen {
syn boolean TokenComponent.isPrimitive() = AttrDecl.isPrimitiveType(getTokenId().getTYPE()); 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() { public String AttrDecl.parameterStructure() {
if (!isParameterized() || (!isMemoized() && !config().visitCheckEnabled())) { if (!isParameterized() || (!isMemoized() && !config().visitCheckEnabled())) {
return ""; return "";
...@@ -498,14 +550,14 @@ aspect JragCodeGen { ...@@ -498,14 +550,14 @@ aspect JragCodeGen {
/** /**
* Generate code to test if two attribute values differ based on the type of the attribute. * Generate code to test if two attribute values differ based on the type of the attribute.
*/ */
public String AttrDecl.valueComparisonExpr(String oldValue, String newValue) { public String AttrDecl.valueComparisonExpr(String newValue, String oldValue) {
if (isPrimitive() || isCircularRewrite()) { if (isPrimitive()) {
return String.format("%s != %s", oldValue, newValue); return String.format("%s != %s", oldValue, newValue);
} else if (declaredNTA() && isCircular()) { } else if (isCircularRewrite()) {
return String.format("!is$Equal(%s, %s)", oldValue, newValue); return String.format("%s != %s || %s.canRewrite()", oldValue, newValue, newValue);
} else { } else {
return String.format("(%s == null && %s != null) || (%s != null && !%s.equals(%s))", return String.format("!AttributeValue.equals(%s, %s)",
oldValue, newValue, oldValue, oldValue, newValue); oldValue, newValue);
} }
} }
...@@ -526,7 +578,7 @@ aspect JragCodeGen { ...@@ -526,7 +578,7 @@ aspect JragCodeGen {
} else { } else {
// Parameterized, circular attribute. // Parameterized, circular attribute.
attr.emitResetMethod(out); attr.emitResetMethod(out);
if (attr.declaredNTA()) { if (attr.declaredNTA() || (config().concurrentEval() && attr.isCircular())) {
attr.emitCacheDeclarations(out); attr.emitCacheDeclarations(out);
} else if (config().lazyMaps()) { } else if (config().lazyMaps()) {
out.format("%sprotected %s %s_values;%n", out.format("%sprotected %s %s_values;%n",
...@@ -677,8 +729,12 @@ aspect JragCodeGen { ...@@ -677,8 +729,12 @@ aspect JragCodeGen {
if (comp.name().equals(attrName) && comp instanceof AggregateComponentNTA if (comp.name().equals(attrName) && comp instanceof AggregateComponentNTA
|| attrName.equals(comp.name() + "Opt") && comp instanceof OptionalComponentNTA || attrName.equals(comp.name() + "Opt") && comp instanceof OptionalComponentNTA
|| attrName.equals(comp.name() + "List") && comp instanceof ListComponentNTA) { || attrName.equals(comp.name() + "List") && comp instanceof ListComponentNTA) {
if (config().concurrentEval()) {
return "setChild(_result, get" + attrName + "ChildPosition());\n";
} else {
return "setChild(" + signature() + "_value, get" + attrName + "ChildPosition());\n"; return "setChild(" + signature() + "_value, get" + attrName + "ChildPosition());\n";
} }
}
// Token components are not stored in child vector. // Token components are not stored in child vector.
return ""; return "";
} else if (declaredNTA()) { // Check if attribute equation is declared NTA. } else if (declaredNTA()) { // Check if attribute equation is declared NTA.
...@@ -843,4 +899,6 @@ aspect Compute { ...@@ -843,4 +899,6 @@ aspect Compute {
syn boolean AttrDecl.simpleCacheCheck() = syn boolean AttrDecl.simpleCacheCheck() =
!config().safeLazy() || isCircular() || declaredNTA() || isAttrNTA(); !config().safeLazy() || isCircular() || declaredNTA() || isAttrNTA();
syn boolean AttrDecl.cacheInCycle() = !simpleCacheCheck();
} }
...@@ -168,9 +168,16 @@ aspect Rewrites { ...@@ -168,9 +168,16 @@ aspect Rewrites {
// this node inherites the rewrittenNode attribute). // this node inherites the rewrittenNode attribute).
CircularRewriteDecl decl = getCircularRewriteDecl(); CircularRewriteDecl decl = getCircularRewriteDecl();
TemplateContext tt = decl.templateContext(); TemplateContext tt = decl.templateContext();
String newValue = "new_" + decl.signature() + "_value"; String newValue, oldValue;
String oldValue = decl.signature() + "_value"; 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("CircularComputeRhs", oldValue + ".rewriteTo()");
}
tt.bind("ChangeCondition", String.format("%s != %s || %s.canRewrite()", tt.bind("ChangeCondition", String.format("%s != %s || %s.canRewrite()",
newValue, oldValue, newValue)); newValue, oldValue, newValue));
tt.bind("BottomValue", "this"); tt.bind("BottomValue", "this");
...@@ -189,6 +196,10 @@ aspect Rewrites { ...@@ -189,6 +196,10 @@ aspect Rewrites {
} }
} }
syn boolean AttrDecl.isCircularNta() = false;
eq CircularRewriteDecl.isCircularNta() = true;
syn lazy CircularRewriteDecl ASTDecl.getCircularRewriteDecl() = syn lazy CircularRewriteDecl ASTDecl.getCircularRewriteDecl() =
new CircularRewriteDecl( new CircularRewriteDecl(
new List<Parameter>(), new List<Parameter>(),
......
...@@ -86,6 +86,14 @@ aspect TemplateUtil { ...@@ -86,6 +86,14 @@ aspect TemplateUtil {
loadTemplates(tt, "trace/Tracer"); loadTemplates(tt, "trace/Tracer");
loadTemplates(tt, "trace/TraceHooks"); 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); return new SimpleContext(tt, this);
} }
......
...@@ -387,6 +387,22 @@ public class Configuration { ...@@ -387,6 +387,22 @@ public class Configuration {
"safe in-cycle caching of non-circular attributes") "safe in-cycle caching of non-circular attributes")
.nonStandard(); .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>(); Collection<String> filenames = new LinkedList<String>();
Option<Boolean> emptyContainerSingletons = new FlagOption("emptyContainerSingletons", Option<Boolean> emptyContainerSingletons = new FlagOption("emptyContainerSingletons",
...@@ -477,6 +493,9 @@ public class Configuration { ...@@ -477,6 +493,9 @@ public class Configuration {
// New since 2.2.4: // New since 2.2.4:
allOptions.add(emptyContainerSingletons); allOptions.add(emptyContainerSingletons);
allOptions.add(concurrentOption);
allOptions.add(numThreadsOption);
allOptions.add(concurrentMap);
// Deprecated in 2.1.5. // Deprecated in 2.1.5.
allOptions.add(doxygenOption); allOptions.add(doxygenOption);
...@@ -593,7 +612,6 @@ public class Configuration { ...@@ -593,7 +612,6 @@ public class Configuration {
tt.bind("CacheCycle", cacheCycle()); tt.bind("CacheCycle", cacheCycle());
tt.bind("StaticState", staticState()); tt.bind("StaticState", staticState());
tt.bind("LazyMaps", lazyMaps()); tt.bind("LazyMaps", lazyMaps());
return root; return root;
} }
...@@ -1303,4 +1321,9 @@ public class Configuration { ...@@ -1303,4 +1321,9 @@ public class Configuration {
public boolean emptyContainerSingletons() { public boolean emptyContainerSingletons() {
return emptyContainerSingletons.value(); return emptyContainerSingletons.value();
} }
/** @return {@code true} if concurrent evaluation is enabled. */
public boolean concurrentEval() {
return concurrentOption.value();
}
} }
...@@ -114,6 +114,14 @@ public class JastAddTask extends Task { ...@@ -114,6 +114,14 @@ public class JastAddTask extends Task {
setOption(config.beaverOption, enable); 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) { public void setLineColumnNumbers(boolean enable) {
setOption(config.lineColumnNumbersOption, enable); setOption(config.lineColumnNumbersOption, enable);
} }
......
...@@ -73,9 +73,6 @@ $endif ...@@ -73,9 +73,6 @@ $endif
/** @apilevel internal */ /** @apilevel internal */
public static final boolean $ASTNode.generatedWithCacheCycle = $CacheCycle; public static final boolean $ASTNode.generatedWithCacheCycle = $CacheCycle;
/** @apilevel internal */
public static final boolean $ASTNode.generatedWithComponentCheck = $ComponentCheck;
$if(!JJTree) $if(!JJTree)
/** @apilevel low-level */ /** @apilevel low-level */
protected $ASTNode $ASTNode.parent; protected $ASTNode $ASTNode.parent;
...@@ -90,6 +87,38 @@ $if(TracingEnabled) ...@@ -90,6 +87,38 @@ $if(TracingEnabled)
} }
$endif $endif
$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) $if(StaticState)
/** @apilevel internal */ /** @apilevel internal */
private static $StateClass $ASTNode.state = new $StateClass(); private static $StateClass $ASTNode.state = new $StateClass();
...@@ -98,10 +127,20 @@ $if(StaticState) ...@@ -98,10 +127,20 @@ $if(StaticState)
public final $StateClass $ASTNode.state() { public final $StateClass $ASTNode.state() {
return state; return state;
} }
/** @apilevel internal */
public final static $StateClass $ASTNode.resetState() {
return state = new $StateClass();
}
$else $else
/** @apilevel internal */ /** @apilevel internal */
private $StateClass $ASTNode.state = null; private $StateClass $ASTNode.state = null;
/** @apilevel internal */
public final void $ASTNode.resetState() {
state = null;
}
/** @apilevel internal */ /** @apilevel internal */
public final $StateClass $ASTNode.state() { public final $StateClass $ASTNode.state() {
if (state == null) { if (state == null) {
...@@ -115,6 +154,7 @@ $else ...@@ -115,6 +154,7 @@ $else
return state; return state;
} }
$endif $endif
$endif
$if(LegacyRewrite) $if(LegacyRewrite)
/** @apilevel internal */ /** @apilevel internal */
......
...@@ -62,13 +62,17 @@ CollDecl.computeMethod:twoPhase [[ ...@@ -62,13 +62,17 @@ CollDecl.computeMethod:twoPhase [[
#rootType root = (#rootType) node; #rootType root = (#rootType) node;
root.survey_#collectionId(); root.survey_#collectionId();
#getType _computedValue = $BottomValue; #getType _computedValue = $BottomValue;
$include(CollDecl.collectContributions)
return _computedValue;
}
]]
CollDecl.collectContributions [[
if (root.contributorMap_#collectionId.containsKey(this)) { if (root.contributorMap_#collectionId.containsKey(this)) {
for ($ASTNode contributor : root.contributorMap_#collectionId.get(this)) { for ($ASTNode contributor : root.contributorMap_#collectionId.get(this)) {
contributor.contributeTo_#(signature)(_computedValue); contributor.contributeTo_#(signature)(_computedValue);
} }
} }
return _computedValue;
}
]] ]]
CollEq.collectContributors:onePhase [[ CollEq.collectContributors:onePhase [[
......
...@@ -46,6 +46,9 @@ $if(IncrementalEnabled) ...@@ -46,6 +46,9 @@ $if(IncrementalEnabled)
node.init_children = null; node.init_children = null;
node.children_computed = null; node.children_computed = null;
inc_copyHandlers(node); inc_copyHandlers(node);
$endif
$if(Concurrent)
node.makeFreshNode();
$endif $endif
return node; return node;
} catch (CloneNotSupportedException e) { } catch (CloneNotSupportedException e) {
......
...@@ -26,28 +26,100 @@ ...@@ -26,28 +26,100 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
ASTState = [[ 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 */ /** @apilevel internal */
public class $StateClass { 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 { protected static class CircularValue {
Object value; Object value;
Cycle cycle; 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 * @apilevel internal
*/ */
protected static class Cycle { 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(); public static final Cycle NON_CYCLE = new Cycle();
/** /**
* Tracks the state of the current circular evaluation. This class defines a * 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 * stack structure where the next element on the stack is pointed to by the
* {@code next} field. * {@code next} field.
*
* @apilevel internal * @apilevel internal
*/ */
protected static class CircleState { protected static class CircleState {
...@@ -71,10 +143,46 @@ $endif ...@@ -71,10 +143,46 @@ $endif
/** Cycle ID of the latest cycle in this circular evaluation. */ /** Cycle ID of the latest cycle in this circular evaluation. */
Cycle cycle = NON_CYCLE; 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) { protected CircleState(CircleState next) {
this.next = 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. */ /** Sentinel circle state representing non-circular evaluation. */
private static final CircleState CIRCLE_BOTTOM = new CircleState(null); private static final CircleState CIRCLE_BOTTOM = new CircleState(null);
...@@ -111,6 +219,84 @@ $endif ...@@ -111,6 +219,84 @@ $endif
circle = next; 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 */ /** @apilevel internal */
protected void leaveCircle() { protected void leaveCircle() {
$if(ComponentCheck) $if(ComponentCheck)
...@@ -241,18 +427,96 @@ $endif ...@@ -241,18 +427,96 @@ $endif
$include(State.incHook) $include(State.incHook)
$if(TracingEnabled) $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; private Trace trace = null;
/** @return the tracer instance used for tracing attribute evaluation in this AST. */ /** @return the tracer instance used for tracing attribute evaluation in this AST. */
public Trace trace() { public Trace trace() {
if (trace == null) { if (trace == null) {
trace = new Trace(); trace = new Trace(receiverFactory.build());
} }
return trace; return trace;
} }
$include(TraceClass) $include(TraceClass)
$endif $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
} }
]] ]]
......
# 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
]]
# 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;
}
}
]]
# 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
}
]]
# 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
}
]]
# 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
}
]]
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
]]
...@@ -90,6 +90,13 @@ public static class Trace { ...@@ -90,6 +90,13 @@ public static class Trace {
Object params, Object value); Object params, Object value);
} }
public Trace(Receiver receiver) {
this.receiver = receiver;
}
public Trace() {
}
// The default event receiver does nothing. // The default event receiver does nothing.
private Receiver receiver = new Receiver() { private Receiver receiver = new Receiver() {
public void accept($StateClass.Trace.Event event, $ASTNode node, String attribute, public void accept($StateClass.Trace.Event event, $ASTNode node, String attribute,
...@@ -105,6 +112,10 @@ public static class Trace { ...@@ -105,6 +112,10 @@ public static class Trace {
this.receiver = receiver; this.receiver = receiver;
} }
public Receiver getReceiver() {
return receiver;
}
/** /**
* Trace that an attribute instance started its computation. * Trace that an attribute instance started its computation.
* @param value The value of the attribute instance. * @param value The value of the attribute instance.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment