From 62d28e6faa09084dfeba49b418be51b2e5ee3945 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jesper=20=C3=96qvist?= <jesper.oqvist@cs.lth.se>
Date: Thu, 4 Feb 2016 00:20:19 +0100
Subject: [PATCH] Safe caching of non-circular attributes

Added --safeLazy option which enables safe in-cycle caching of non-circular
attributes.
---
 ChangeLog                               | 10 +++++
 src/jastadd/ast/JragCodeGen.jrag        |  9 ++++
 src/java/org/jastadd/Configuration.java | 14 +++++++
 src/java/org/jastadd/JastAddTask.java   |  8 ++++
 src/template/ast/ASTNode.tt             |  3 +-
 src/template/ast/Attributes.tt          | 55 +++++++++++++++++++++++--
 src/template/ast/State.tt               |  5 ++-
 src/template/flush/Flush.tt             |  9 +++-
 8 files changed, 107 insertions(+), 6 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 6be4b5de..798df6c9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2016-03-21  Jesper Öqvist <jesper.oqvist@cs.lth.se>
+
+    * Added --safeLazy option which allows non-circular attributes to be
+    safely cached during circular evaluation, even if the attribute is
+    effectively circular. The --safeLazy option adds an extra cache field for
+    each attributes which tracks the last cycle on which the attribute was
+    evaluated. The next time the attribute is accessed it checks if it can
+    reuse the last cached value based on the current cycle state and the last
+    cached cycle state.
+
 2016-03-16  Jesper Öqvist <jesper.oqvist@cs.lth.se>
 
     * Improved circular NTA rewrite implementation: implicit rewrite
diff --git a/src/jastadd/ast/JragCodeGen.jrag b/src/jastadd/ast/JragCodeGen.jrag
index bb240e0d..aa4ce8a5 100644
--- a/src/jastadd/ast/JragCodeGen.jrag
+++ b/src/jastadd/ast/JragCodeGen.jrag
@@ -226,6 +226,10 @@ aspect JragCodeGen {
           sb.append(String.format("if (%s_visited == null) %s_visited = %s;\n",
               signature(), signature(), config().createDefaultSet()));
         }
+        if (getNumParameter() != 0 && isLazy() && !simpleCacheCheck()) {
+          sb.append(String.format("if (%s_computed == null) %s_computed = %s;\n",
+              signature(), signature(), config().createDefaultMap()));
+        }
       }
       if (getNumParameter() != 0 && (isLazy() || isCircular())) {
         sb.append(String.format("if (%s_values == null) %s_values = %s;\n",
@@ -791,4 +795,9 @@ aspect Compute {
 
   eq CollDecl.circularComputeRhs() = String.format("combine_%s_contributions(%s)",
       signature(), getBottomValue());
+
+  syn boolean AttrDecl.simpleCacheCheck() =
+      config().safeLazy()
+      ? (isCircular() || declaredNTA() || isAttrNTA())
+      : true;
 }
diff --git a/src/java/org/jastadd/Configuration.java b/src/java/org/jastadd/Configuration.java
index 03c03737..28021029 100644
--- a/src/java/org/jastadd/Configuration.java
+++ b/src/java/org/jastadd/Configuration.java
@@ -381,6 +381,10 @@ public class Configuration {
   Option<Boolean> dotOption = new FlagOption("dot", "generate a Dot graph from the grammar")
       .nonStandard();
 
+  Option<Boolean> safeLazyOption = new FlagOption("safeLazy",
+      "safe in-cycle caching of non-circular attributes")
+      .nonStandard();
+
   Collection<String> filenames = new LinkedList<String>();
 
   /**
@@ -458,6 +462,9 @@ public class Configuration {
     // New since 2.1.12.
     allOptions.add(stateClassNameOption);
 
+    // New since 2.2.1:
+    allOptions.add(safeLazyOption);
+
     // Deprecated in 2.1.5.
     allOptions.add(doxygenOption);
     allOptions.add(cacheAllOption);
@@ -1264,4 +1271,11 @@ public class Configuration {
   public boolean generateImplicits() {
     return generateImplicitsOption.value();
   }
+
+  /**
+   * @return {@code true} if safe lazy attribute caching in circular evaluation should be used.
+   */
+  public boolean safeLazy() {
+    return safeLazyOption.value();
+  }
 }
diff --git a/src/java/org/jastadd/JastAddTask.java b/src/java/org/jastadd/JastAddTask.java
index d362f242..e02c7720 100644
--- a/src/java/org/jastadd/JastAddTask.java
+++ b/src/java/org/jastadd/JastAddTask.java
@@ -274,6 +274,14 @@ public class JastAddTask extends Task {
     setOption(config.minListSizeOption, arg);
   }
 
+  public void setDot(boolean enable) {
+    setOption(config.dotOption, enable);
+  }
+
+  public void setSafeLazy(boolean enable) {
+    setOption(config.safeLazyOption, enable);
+  }
+
   @Override
   public void execute() throws BuildException {
     System.err.println("generating node types and weaving aspects");
diff --git a/src/template/ast/ASTNode.tt b/src/template/ast/ASTNode.tt
index 4962dba5..310849e5 100644
--- a/src/template/ast/ASTNode.tt
+++ b/src/template/ast/ASTNode.tt
@@ -199,8 +199,9 @@ $if(DebugMode)
 
     while (node != null && !node.debugNodeAttachmentIsRoot()) {
 $if(LegacyRewrite)
-      if (node.in$$Circle())
+      if (node.in$$Circle()) {
         return;
+      }
 $endif
       $ASTNode parent = ($ASTNode) node.parent;
       if (parent != null && parent.getIndexOfChild(node) == -1) {
diff --git a/src/template/ast/Attributes.tt b/src/template/ast/Attributes.tt
index 6957419f..84db75b9 100644
--- a/src/template/ast/Attributes.tt
+++ b/src/template/ast/Attributes.tt
@@ -41,7 +41,11 @@ $endif
 AttrDecl.cacheDeclarations [[
 $if(!#isParameterized)
   /** @apilevel internal */
+  $if(#simpleCacheCheck)
   protected boolean #(signature)_computed = false;
+  $else
+  protected $StateClass.Cycle #(signature)_computed = null;
+  $endif
 
   /** @apilevel internal */
   protected #getType #(signature)_value;
@@ -61,9 +65,14 @@ $else
   /** @apilevel internal */
   protected $DefaultMapType #(signature)_values = $CreateDefaultMap;
 $endif
+  $if(!#simpleCacheCheck)
+  /** @apilevel internal */
+  protected $DefaultMapType #(signature)_computed;
+  $endif
 $endif
 ]]
 
+# Method headers for attribute declarations.
 AttrDecl.synDecl = AttrDecl.inhDecl [[
 #docComment
   #annotations
@@ -291,7 +300,11 @@ AttrDecl.cacheCheck [[
 $if(#hasCache)
 $include(AttrDecl.incHookAttrRead)
   $if(!#isParameterized)
+    $if(#simpleCacheCheck)
 if (#(signature)_computed) {
+    $else
+if (#(signature)_computed == $StateClass.NON_CYCLE || #(signature)_computed == state().cycle()) {
+    $endif
   $include(AttrDecl.traceCacheRead)
     $if(#isAttrNTA)
   return (#boxedType) getChild(#(signature)ChildPosition());
@@ -300,7 +313,13 @@ if (#(signature)_computed) {
     $endif
 }
   $else
+    $if(#simpleCacheCheck)
 if (#(signature)_values.containsKey(_parameters)) {
+    $else
+if (#(signature)_values.containsKey(_parameters) && #(signature)_computed != null
+    && #(signature)_computed.containsKey(_parameters)
+    && (#(signature)_computed.get(_parameters) == $StateClass.NON_CYCLE || #(signature)_computed.get(_parameters) == state().cycle())) {
+    $endif
   $include(AttrDecl.traceCacheRead)
     $if(#isAttrNTA)
   return (#boxedType) getChild(#(signature)ChildPosition()));
@@ -318,14 +337,40 @@ $if(#isLazy)
   $if(LegacyRewrite)
 if (#cacheStoreCondition) {
   $endif
-  $if(#isParameterized)
+  $if(#simpleCacheCheck)
+    $if(#isParameterized)
+$include(AttrDecl.incHookAttrCompBeforeStore)
+#(signature)_values.put(_parameters, #(signature)_value);
+$include(AttrDecl.traceCacheStore)
+    $else
+$include(AttrDecl.incHookAttrCompBeforeStore)
+#(signature)_computed = true;
+$include(AttrDecl.traceCacheStore)
+    $endif
+  $else
+if (state().inCircle()) {
+    $if(#isParameterized)
   $include(AttrDecl.incHookAttrCompBeforeStore)
   #(signature)_values.put(_parameters, #(signature)_value);
+  #(signature)_computed.put(_parameters, state().cycle());
   $include(AttrDecl.traceCacheStore)
-  $else
+    $else
+  $include(AttrDecl.incHookAttrCompBeforeStore)
+  #(signature)_computed = state().cycle();
+  $include(AttrDecl.traceCacheStore)
+    $endif
+} else {
+    $if(#isParameterized)
+  $include(AttrDecl.incHookAttrCompBeforeStore)
+  #(signature)_values.put(_parameters, #(signature)_value);
+  #(signature)_computed.put(_parameters, $StateClass.NON_CYCLE);
+  $include(AttrDecl.traceCacheStore)
+    $else
   $include(AttrDecl.incHookAttrCompBeforeStore)
-  #(signature)_computed = true;
+  #(signature)_computed = $StateClass.NON_CYCLE;
   $include(AttrDecl.traceCacheStore)
+    $endif
+}
   $endif
   $if(LegacyRewrite)
 } else {
@@ -363,7 +408,9 @@ state().assertSameCircle(#(signature)_circle, "#hostClassName.#signatureJavaStyl
 state().enterLazyAttribute();
 $else
   $if(#isLazy)
+    $if(#simpleCacheCheck)
 state().enterLazyAttribute();
+    $endif
   $endif
 $endif
 ]]
@@ -373,7 +420,9 @@ $if(ComponentCheck)
 state().leaveLazyAttribute();
 $else
   $if(#isLazy)
+    $if(#simpleCacheCheck)
 state().leaveLazyAttribute();
+    $endif
   $endif
 $endif
 ]]
diff --git a/src/template/ast/State.tt b/src/template/ast/State.tt
index 6cd3d553..31b6963b 100644
--- a/src/template/ast/State.tt
+++ b/src/template/ast/State.tt
@@ -41,6 +41,9 @@ public class $StateClass {
   protected static class Cycle {
   }
 
+  /** The cycle ID used outside of circular evaluation. */
+  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
@@ -67,7 +70,7 @@ $if(ComponentCheck)
 
 $endif
     /** Cycle ID of the latest cycle in this circular evaluation. */
-    Cycle cycle = null;
+    Cycle cycle = NON_CYCLE;
 
     protected CircleState(CircleState next) {
       this.next = next;
diff --git a/src/template/flush/Flush.tt b/src/template/flush/Flush.tt
index c438ebc8..37219f83 100644
--- a/src/template/flush/Flush.tt
+++ b/src/template/flush/Flush.tt
@@ -128,6 +128,9 @@ $endif
 AttrDecl.resetAttrCache [[
 $if(#isLazy)
   $if(#isParameterized)
+    $if(!#simpleCacheCheck)
+#(signature)_computed = $CreateDefaultMap;
+    $endif
     $if(LazyMaps)
 #(signature)_values = null;
     $else
@@ -137,9 +140,13 @@ $if(#isLazy)
 #(signature)_list = null;
     $endif
   $else
+    $if(#simpleCacheCheck)
 #(signature)_computed = false;
-    $if(#isCircular)
+      $if(#isCircular)
 #(signature)_initialized = false;
+      $endif
+    $else
+#(signature)_computed = null;
     $endif
     $if(!#isPrimitive)
 #(signature)_value = null;
-- 
GitLab