From ac4f00524ec510b880506806192e500b8c3a7c29 Mon Sep 17 00:00:00 2001 From: Terence Parr Date: Tue, 24 Jul 2012 14:19:43 -0700 Subject: [PATCH] add ctx cache. --- .../antlr/v4/runtime/atn/ATNConfigSet.java | 41 +++++++++++++------ .../antlr/v4/runtime/atn/ATNSimulator.java | 28 ++++++++++--- .../v4/runtime/atn/ParserATNSimulator.java | 21 ++++++---- .../v4/runtime/atn/PredictionContext.java | 6 +-- .../runtime/atn/PredictionContextCache.java | 41 +++++++++++++++++++ tool/playground/U.g | 38 ++++------------- 6 files changed, 115 insertions(+), 60 deletions(-) create mode 100644 runtime/Java/src/org/antlr/v4/runtime/atn/PredictionContextCache.java diff --git a/runtime/Java/src/org/antlr/v4/runtime/atn/ATNConfigSet.java b/runtime/Java/src/org/antlr/v4/runtime/atn/ATNConfigSet.java index 2489623c5..2cc793326 100755 --- a/runtime/Java/src/org/antlr/v4/runtime/atn/ATNConfigSet.java +++ b/runtime/Java/src/org/antlr/v4/runtime/atn/ATNConfigSet.java @@ -30,6 +30,7 @@ package org.antlr.v4.runtime.atn; import org.antlr.v4.runtime.misc.IntervalSet; +import org.antlr.v4.runtime.misc.Nullable; import java.util.ArrayList; import java.util.Collection; @@ -102,8 +103,8 @@ public class ATNConfigSet implements Set { public ATNConfigSet(boolean fullCtx) { this.fullCtx = fullCtx; } public ATNConfigSet() { this.fullCtx = true; } - public ATNConfigSet(ATNConfigSet old) { - addAll(old); + public ATNConfigSet(ATNConfigSet old, PredictionContextCache contextCache) { + addAll(old, contextCache); this.fullCtx = old.fullCtx; this.uniqueAlt = old.uniqueAlt; this.conflictingAlts = old.conflictingAlts; @@ -111,24 +112,32 @@ public class ATNConfigSet implements Set { this.dipsIntoOuterContext = old.dipsIntoOuterContext; } + @Override + public boolean add(ATNConfig e) { + return add(e, null); + } + /** Adding a new config means merging contexts with existing configs for * (s, i, pi, _) * We use (s,i,pi) as key */ - @Override - public boolean add(ATNConfig value) { - Key key = new Key(value); + public boolean add(ATNConfig config, @Nullable PredictionContextCache contextCache) { + Key key = new Key(config); ATNConfig existing = configToContext.get(key); if ( existing==null ) { // nothing there yet; easy, just add - configToContext.put(key, value); + configToContext.put(key, config); return true; } // a previous (s,i,pi,_), merge with it and save result boolean rootIsWildcard = !fullCtx; PredictionContext merged = - PredictionContext.merge(existing.context, value.context, rootIsWildcard); + PredictionContext.merge(existing.context, config.context, rootIsWildcard); + // no need to check for existing.context, config.context in cache + // since only way to create new graphs is "call rule" and here. We + // cache at both places. + if ( contextCache!=null ) merged = contextCache.add(merged); existing.reachesIntoOuterContext = - Math.max(existing.reachesIntoOuterContext, value.reachesIntoOuterContext); + Math.max(existing.reachesIntoOuterContext, config.reachesIntoOuterContext); existing.context = merged; // replace context; no need to alt mapping return true; } @@ -161,17 +170,23 @@ public class ATNConfigSet implements Set { if ( configToContext.isEmpty() ) return; for (ATNConfig config : configToContext.values()) { - int before = PredictionContext.getAllContextNodes(config.context).size(); +// int before = PredictionContext.getAllContextNodes(config.context).size(); config.context = interpreter.getCachedContext(config.context); - int after = PredictionContext.getAllContextNodes(config.context).size(); - System.out.println("configs "+before+"->"+after); +// int after = PredictionContext.getAllContextNodes(config.context).size(); +// System.out.println("configs "+before+"->"+after); } } @Override - public boolean addAll(Collection coll) { + public boolean addAll(Collection c) { + return addAll(c, null); + } + + public boolean addAll(Collection coll, + PredictionContextCache contextCache) + { for (ATNConfig c : coll) { - add(c); + add(c, contextCache); } return false; } diff --git a/runtime/Java/src/org/antlr/v4/runtime/atn/ATNSimulator.java b/runtime/Java/src/org/antlr/v4/runtime/atn/ATNSimulator.java index c53c1c8d8..9ef918979 100644 --- a/runtime/Java/src/org/antlr/v4/runtime/atn/ATNSimulator.java +++ b/runtime/Java/src/org/antlr/v4/runtime/atn/ATNSimulator.java @@ -34,10 +34,8 @@ import org.antlr.v4.runtime.misc.IntervalSet; import org.antlr.v4.runtime.misc.NotNull; import java.util.ArrayList; -import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; -import java.util.Map; public abstract class ATNSimulator { /** Must distinguish between missing edge and edge we know leads nowhere */ @@ -47,14 +45,32 @@ public abstract class ATNSimulator { public final ATN atn; /** The context cache maps all PredictionContext objects that are equals() - * to a single cached copy. This cache be shared across all contexts + * to a single cached copy. This cache is shared across all contexts * in all ATNConfigs in all DFA states. We rebuild each ATNConfigSet * to use only cached nodes/graphs in addDFAState(). We don't want to * fill this during closure() since there are lots of contexts that * pop up but are not used ever again. It also greatly slows down closure(). */ - protected final Map contextCache = - new HashMap(); + protected final PredictionContextCache sharedContextCache = + new PredictionContextCache("shared DFA state context cache"); + + /** This context cache tracks all context graphs used during a single + * ATN-based prediction operation. There will be significant context graph + * sharing among ATNConfigSets because all sets are derived from the + * same starting context. + * + * This cache is blown away after each adaptivePredict() + * because we cache everything within ATNConfigSets that become DFA + * states in sharedContextCache. (Sam thinks of this as an analogy to + * the nursery in a generational GC; then, sharedContextCache would be + * the mature generation.) + * + * In Sam's version, this is a parameter passed down through all of + * the methods, but it gets pretty unwieldy as there are already + * a crapload of parameters. Consequently, I'm using a field as a + * "parameter" despite it being generally poor coding style. + */ + protected PredictionContextCache contextCache; static { ERROR = new DFAState(new ATNConfigSet()); @@ -71,7 +87,7 @@ public abstract class ATNSimulator { IdentityHashMap visited = new IdentityHashMap(); return PredictionContext.getCachedContext(context, - contextCache, + sharedContextCache, visited); } diff --git a/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java b/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java index 114fba762..8a1d97154 100755 --- a/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java +++ b/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java @@ -240,8 +240,8 @@ import java.util.Set; * holds the decision were evaluating */ public class ParserATNSimulator extends ATNSimulator { - public static boolean debug = true; - public static boolean debug_list_atn_decisions = false; + public static boolean debug = false; + public static boolean debug_list_atn_decisions = true; public static boolean dfa_debug = false; public static boolean retry_debug = false; @@ -316,6 +316,7 @@ public class ParserATNSimulator extends ATNSimulator { public int predictATN(@NotNull DFA dfa, @NotNull TokenStream input, @Nullable ParserRuleContext outerContext) { + contextCache = new PredictionContextCache("predict ctx cache"); if ( outerContext==null ) outerContext = ParserRuleContext.EMPTY; if ( debug || debug_list_atn_decisions ) { System.out.println("ATN decision "+dfa.decision+ @@ -344,6 +345,7 @@ public class ParserATNSimulator extends ATNSimulator { throw nvae; } finally { + contextCache = null; // wack the cache input.seek(index); input.release(m); } @@ -964,7 +966,7 @@ public class ParserATNSimulator extends ATNSimulator { // don't see past end of a rule for any nongreedy decision if ( debug ) System.out.println("NONGREEDY at stop state of "+ getRuleName(config.state.ruleIndex)); - configs.add(config); + configs.add(config, contextCache); return; } // We hit rule end. If we have context info, use it @@ -1007,6 +1009,7 @@ public class ParserATNSimulator extends ATNSimulator { { config.context = new SingletonPredictionContext(config.context, config.state.stateNumber); + if ( contextCache!=null ) config.context = contextCache.add(config.context); // alter config; it's ok, since all calls to closure pass in a fresh config for us to chase if ( debug ) System.out.println("Loop back; push "+config.state.stateNumber+", stack="+config.context); } @@ -1033,7 +1036,7 @@ public class ParserATNSimulator extends ATNSimulator { ATNState p = config.state; // optimization if ( !p.onlyHasEpsilonTransitions() ) { - configs.add(config); + configs.add(config, contextCache); if ( config.semanticContext!=null && config.semanticContext!= SemanticContext.NONE ) { configs.hasSemanticContext = true; } @@ -1141,9 +1144,9 @@ public class ParserATNSimulator extends ATNSimulator { System.out.println("CALL rule "+getRuleName(t.target.ruleIndex)+ ", ctx="+config.context); } - ATNState p = config.state; PredictionContext newContext = - new SingletonPredictionContext(config.context, p.stateNumber); + new SingletonPredictionContext(config.context, config.state.stateNumber); + if ( contextCache!=null ) newContext = contextCache.add(newContext); return new ATNConfig(config, t.target, newContext); } @@ -1446,10 +1449,10 @@ public class ParserATNSimulator extends ATNSimulator { DFAState newState = proposed; newState.stateNumber = dfa.states.size(); - System.out.println("Before opt, cache size = "+contextCache.size()); +// System.out.println("Before opt, cache size = "+ sharedContextCache.size()); configs.optimizeConfigs(this); - System.out.println("After opt, cache size = " + contextCache.size()); - newState.configs = new ATNConfigSet(configs); +// System.out.println("After opt, cache size = " + sharedContextCache.size()); + newState.configs = new ATNConfigSet(configs, contextCache); dfa.states.put(newState, newState); if ( debug ) System.out.println("adding new DFA state: "+newState); diff --git a/runtime/Java/src/org/antlr/v4/runtime/atn/PredictionContext.java b/runtime/Java/src/org/antlr/v4/runtime/atn/PredictionContext.java index 55de4828e..ff8e6efc6 100644 --- a/runtime/Java/src/org/antlr/v4/runtime/atn/PredictionContext.java +++ b/runtime/Java/src/org/antlr/v4/runtime/atn/PredictionContext.java @@ -389,7 +389,7 @@ public abstract class PredictionContext implements Iterable contextCache, + @NotNull PredictionContextCache contextCache, @NotNull IdentityHashMap visited) { if (context.isEmpty()) { @@ -426,7 +426,7 @@ public abstract class PredictionContext implements Iterable cache = + new HashMap(); + + public PredictionContextCache(String name) { + this.name = name; + } + + /** Add a context to the cache and return it. If the context already exists, + * return that one instead and do not add a new context to the cache. + */ + public PredictionContext add(PredictionContext ctx) { + if ( ctx==PredictionContext.EMPTY ) return PredictionContext.EMPTY; + PredictionContext existing = cache.get(ctx); + if ( existing!=null ) { + System.out.println(name+" reuses "+existing); + return existing; + } + cache.put(ctx, ctx); + return ctx; + } + + public PredictionContext get(PredictionContext ctx) { + return cache.get(ctx); + } + + public int size() { + return cache.size(); + } +} diff --git a/tool/playground/U.g b/tool/playground/U.g index a5f215ea0..8e6aca610 100644 --- a/tool/playground/U.g +++ b/tool/playground/U.g @@ -1,32 +1,12 @@ -grammar U; +lexer grammar U; -@members {public static boolean java5 = true;} +X : 'a' -> skip ; +Y : 'z' -> skip, more ; +// (RULE C (BLOCK (LEXER_ALT_ACTION (ALT 'x') (LEXER_ACTION_CALL mode ISLAND)))) +C: 'x' -> mode(ISLAND) ; +// (RULE A (BLOCK (LEXER_ALT_ACTION (ALT 'a') (LEXER_ACTION_CALL mode ISLAND) skip))) +A: 'b' -> mode(ISLAND), skip ; -prog: ( enumDecl - | stat - )* - EOF - ; +mode INSIDE; -enumDecl - : {java5}? 'enum' ID '{' ID (',' ID)* '}' - ; - -args - : arg (',' arg )* - ; - -arg - : INT - ; - -stat: ID '=' expr ';' ; - -expr: ID {System.out.println("ID "+$ID.text);} - | {!java5}? 'enum' {System.out.println("ID enum");} - | INT - ; - -ID : [a-zA-Z]+ ; -INT : [0-9]+ ; -WS : [ \t\n\r]+ -> skip ; +B : '<' ;