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.