add ctx cache.

This commit is contained in:
Terence Parr 2012-07-24 14:19:43 -07:00
parent 9627652b67
commit ac4f00524e
6 changed files with 115 additions and 60 deletions

View File

@ -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<ATNConfig> {
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<ATNConfig> {
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<ATNConfig> {
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<? extends ATNConfig> coll) {
public boolean addAll(Collection<? extends ATNConfig> c) {
return addAll(c, null);
}
public boolean addAll(Collection<? extends ATNConfig> coll,
PredictionContextCache contextCache)
{
for (ATNConfig c : coll) {
add(c);
add(c, contextCache);
}
return false;
}

View File

@ -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<PredictionContext, PredictionContext> contextCache =
new HashMap<PredictionContext, PredictionContext>();
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<PredictionContext, PredictionContext> visited =
new IdentityHashMap<PredictionContext, PredictionContext>();
return PredictionContext.getCachedContext(context,
contextCache,
sharedContextCache,
visited);
}

View File

@ -240,8 +240,8 @@ import java.util.Set;
* holds the decision were evaluating
*/
public class ParserATNSimulator<Symbol extends Token> 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<Symbol extends Token> 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<Symbol extends Token> extends ATNSimulator {
throw nvae;
}
finally {
contextCache = null; // wack the cache
input.seek(index);
input.release(m);
}
@ -964,7 +966,7 @@ public class ParserATNSimulator<Symbol extends Token> 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<Symbol extends Token> 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<Symbol extends Token> 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<Symbol extends Token> 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<Symbol extends Token> 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);

View File

@ -389,7 +389,7 @@ public abstract class PredictionContext implements Iterable<SingletonPredictionC
// From Sam
public static PredictionContext getCachedContext(
@NotNull PredictionContext context,
@NotNull Map<PredictionContext, PredictionContext> contextCache,
@NotNull PredictionContextCache contextCache,
@NotNull IdentityHashMap<PredictionContext, PredictionContext> visited)
{
if (context.isEmpty()) {
@ -426,7 +426,7 @@ public abstract class PredictionContext implements Iterable<SingletonPredictionC
}
if (!changed) {
contextCache.put(context, context);
contextCache.add(context);
visited.put(context, context);
return context;
}
@ -443,7 +443,7 @@ public abstract class PredictionContext implements Iterable<SingletonPredictionC
updated = new ArrayPredictionContext(parents, arrayPredictionContext.invokingStates);
}
contextCache.put(updated, updated);
contextCache.add(updated);
visited.put(updated, updated);
visited.put(context, updated);

View File

@ -0,0 +1,41 @@
package org.antlr.v4.runtime.atn;
import java.util.HashMap;
import java.util.Map;
/** Used to cache PredictionContext objects. Its use for both the shared
* context cash associated with contacts in DFA states as well as the
* transient cash used for adaptivePredict(). This cache can be used for
* both lexers and parsers.
*/
public class PredictionContextCache {
protected String name;
protected Map<PredictionContext, PredictionContext> cache =
new HashMap<PredictionContext, PredictionContext>();
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();
}
}

View File

@ -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 : '<' ;