/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.reasoner.rulesys.impl; import java.util.*; import org.apache.jena.graph.* ; import org.apache.jena.reasoner.* ; import org.apache.jena.reasoner.rulesys.* ; import org.apache.jena.reasoner.rulesys.impl.RuleClauseCode.CompileState.RuleClauseCodeList ; import org.apache.jena.util.PrintUtil ; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Bytecode interpeter engine for the LP version of the backward * chaining rule system. An instance of this is forked off for each * parallel query. */ public class LPInterpreter { // ======================================================================= // Variables /** The engine which is using this interpreter */ protected LPBRuleEngine engine; /** The execution context that should be notified of suspended branches */ protected LPInterpreterContext iContext; /** True if the engine has terminated */ protected boolean isComplete = false; /** The set of temporary variables (Ti) in use by this interpreter */ protected Node[] tVars = new Node[RuleClauseCode.MAX_TEMPORARY_VARS]; /** The set of argument variables (Ai) in use by this interpreter */ protected Node[] argVars = new Node[RuleClauseCode.MAX_ARGUMENT_VARS]; /** The set of "permanent" variables (Yi) in use by this interpreter */ protected Node[] pVars = null; /** The current environment frame */ protected EnvironmentFrame envFrame; /** The current choice point frame */ protected FrameObject cpFrame; /** The trail of variable bindings that have to be unwound on backtrack */ protected ArrayList<Node> trail = new ArrayList<>(); /** The execution context description to be passed to builtins */ protected RuleContext context; /** Trick to allow the very top level triple lookup to return results with reduced store turnover */ protected TopLevelTripleMatchFrame topTMFrame; /** Original set up goal, only used for debugging */ protected TriplePattern goal; static Logger logger = LoggerFactory.getLogger(LPInterpreter.class); // ======================================================================= // Constructors /** * Constructor used to construct top level calls. * @param engine the engine which is calling this interpreter * @param goal the query to be satisfied */ public LPInterpreter(LPBRuleEngine engine, TriplePattern goal) { this(engine, goal, engine.getRuleStore().codeFor(goal), true); } /** * Constructor. * @param engine the engine which is calling this interpreter * @param goal the query to be satisfied * @param isTop true if this is a top level call from the outside iterator, false means it is an * internal generator call which means we don't need to insert an tabled call */ public LPInterpreter(LPBRuleEngine engine, TriplePattern goal, boolean isTop) { this(engine, goal, engine.getRuleStore().codeFor(goal), isTop); } /** * Constructor. * @param engine the engine which is calling this interpreter * @param goal the query to be satisfied * @param clauses the set of code blocks needed to implement this goal * @param isTop true if this is a top level call from the outside iterator, false means it is an * internal generator call which means we don't need to insert an tabled call */ public LPInterpreter(LPBRuleEngine engine, TriplePattern goal, List<RuleClauseCode> clauses, boolean isTop) { this.engine = engine; this.goal = goal; // Used for debug only // Construct dummy top environemnt which is a call into the clauses for this goal if (engine.getDerivationLogging()) { envFrame = new EnvironmentFrameWithDerivation(RuleClauseCode.returnCodeBlock); } else { envFrame = new EnvironmentFrame(RuleClauseCode.returnCodeBlock); } envFrame.allocate(RuleClauseCode.MAX_PERMANENT_VARS); HashMap<Node, Node> mappedVars = new HashMap<>(); envFrame.pVars[0] = argVars[0] = standardize(goal.getSubject(), mappedVars); envFrame.pVars[1] = argVars[1] = standardize(goal.getPredicate(), mappedVars); envFrame.pVars[2] = argVars[2] = standardize(goal.getObject(), mappedVars); if (engine.getDerivationLogging()) { ((EnvironmentFrameWithDerivation)envFrame).initDerivationRecord(argVars); } if (clauses != null && clauses.size() > 0) { if (isTop && engine.getRuleStore().isTabled(goal)) { setupTabledCall(0, 0); // setupClauseCall(0, 0, clauses); } else { setupClauseCall(0, 0, clauses, goal.isGround()); } } // TripleMatchFrame tmFrame = new TripleMatchFrame(this); topTMFrame = new TopLevelTripleMatchFrame(this, goal); topTMFrame.linkTo(cpFrame); topTMFrame.setContinuation(0, 0); cpFrame = topTMFrame; } /** * Called by top level interpeter to set to execution context for this interpeter to be * top level instead of an internal generator. */ public void setTopInterpreter(LPInterpreterContext context) { iContext = context; FrameObject topChoice = topTMFrame.getLink(); if (topChoice instanceof ConsumerChoicePointFrame) { ((ConsumerChoicePointFrame)topChoice).context = context; } } // ======================================================================= // Control methods /** * Stop the current work. This is called if the top level results iterator has * either finished or the calling application has had enough. */ public void close() { isComplete = true; if (cpFrame != null) cpFrame.close(); engine.detach(this); } /** * Start the interpreter running with the given context. */ public void setState(LPInterpreterState state) { if (state instanceof ConsumerChoicePointFrame) { restoreState((ConsumerChoicePointFrame) state); } else { iContext = (LPInterpreterContext) state; } } /** * Return the next result from this engine, no further initialization. * Should be called from within an appropriately synchronized block. */ public Object next() { boolean traceOn = engine.isTraceOn(); // System.out.println("next() on interpeter for goal " + goal); StateFlag answer = run(); // System.out.println("end next() on interpeter for goal " + goal); if (answer == StateFlag.FAIL || answer == StateFlag.SUSPEND) { return answer; } else if (answer == StateFlag.SATISFIED) { if (traceOn) logger.info("RETURN: " + topTMFrame.lastMatch); return topTMFrame.lastMatch; } else { Triple t = new Triple(deref(pVars[0]), deref(pVars[1]), derefPossFunctor(pVars[2])); if (traceOn) logger.info("RETURN: " + t); return t; } } /** * Preserve the current interpreter state in the given * @return */ /** * Return the engine which owns this interpreter. */ public LPBRuleEngine getEngine() { return engine; } /** * Return the current choice point frame that can be used to restart the * interpter at this point. */ public FrameObject getChoiceFrame() { return cpFrame; } /** * Return the context in which this interpreter is running, that is * either the Generator for a tabled goal or a top level iterator. */ public LPInterpreterContext getContext() { return iContext; } // ======================================================================= // Engine implementation /** * Restore the current choice point and restart execution of the LP code * until either find a successful branch (in which case exit with StateFlag.ACTIVE * and variables bound to the correct results) or exhaust all choice points (in * which case exit with StateFlag.FAIL and no bound results). In future tabled * version could also exit with StateFlag.SUSPEND in cases whether the intepreter * needs to suspend to await tabled results from a parallel proof tree. */ protected StateFlag run() { int pc = 0; // Program code counter int ac = 0; // Program arg code counter RuleClauseCode clause = null; // The clause being executed ChoicePointFrame choice = null; byte[] code; Object[] args; boolean traceOn = engine.isTraceOn(); boolean recordDerivations = engine.getDerivationLogging(); main: while (cpFrame != null) { // restore choice point if (cpFrame instanceof ChoicePointFrame) { choice = (ChoicePointFrame)cpFrame; if (!choice.hasNext()) { // No more choices left in this choice point cpFrame = choice.getLink(); if (traceOn) logger.info("FAIL in clause " + choice.envFrame.clause + " choices exhausted"); continue main; } clause = choice.nextClause(); // Create an execution environment for the new choice of clause if (recordDerivations) { envFrame = new EnvironmentFrameWithDerivation(clause); } else { envFrame = new EnvironmentFrame(clause); } envFrame.linkTo(choice.envFrame); envFrame.cpc = choice.cpc; envFrame.cac = choice.cac; // Restore the choice point state System.arraycopy(choice.argVars, 0, argVars, 0, RuleClauseCode.MAX_ARGUMENT_VARS); int trailMark = choice.trailIndex; if (trailMark < trail.size()) { unwindTrail(trailMark); } pc = ac = 0; if (recordDerivations) { ((EnvironmentFrameWithDerivation)envFrame).initDerivationRecord(argVars); } if (traceOn) logger.info("ENTER " + clause + " : " + getArgTrace()); // then fall through into the recreated execution context for the new call } else if (cpFrame instanceof TripleMatchFrame) { TripleMatchFrame tmFrame = (TripleMatchFrame)cpFrame; // Restore the calling context envFrame = tmFrame.envFrame; clause = envFrame.clause; int trailMark = tmFrame.trailIndex; if (trailMark < trail.size()) { unwindTrail(trailMark); } // Find the next choice result directly if (!tmFrame.nextMatch(this)) { // No more matches cpFrame = cpFrame.getLink(); if (traceOn) logger.info("TRIPLE match (" + tmFrame.goal +") -> FAIL"); continue main; } if (traceOn) { logger.info("TRIPLE match (" + tmFrame.goal +") -> " + getArgTrace()); logger.info("RENTER " + clause); } pc = tmFrame.cpc; ac = tmFrame.cac; if (recordDerivations) { if (envFrame instanceof EnvironmentFrameWithDerivation) { ((EnvironmentFrameWithDerivation)envFrame).noteMatch(tmFrame.goal, pc); } } // then fall through to the execution context in which the the match was called } else if (cpFrame instanceof TopLevelTripleMatchFrame) { TopLevelTripleMatchFrame tmFrame = (TopLevelTripleMatchFrame)cpFrame; // Find the next choice result directly if (!tmFrame.nextMatch(this)) { // No more matches cpFrame = cpFrame.getLink(); if (traceOn) logger.info("TRIPLE match (" + tmFrame.goal +") -> FAIL"); continue main; } else { // Match but this is the top level so return the triple directly if (traceOn) logger.info("TRIPLE match (" + tmFrame.goal +") ->"); return StateFlag.SATISFIED; } } else if (cpFrame instanceof ConsumerChoicePointFrame) { ConsumerChoicePointFrame ccp = (ConsumerChoicePointFrame)cpFrame; // Restore the calling context envFrame = ccp.envFrame; clause = envFrame.clause; if (traceOn) logger.info("RESTORE " + clause + ", due to tabled goal " + ccp.generator.goal); int trailMark = ccp.trailIndex; if (trailMark < trail.size()) { unwindTrail(trailMark); } // Find the next choice result directly StateFlag state = ccp.nextMatch(this); if (state == StateFlag.FAIL) { // No more matches cpFrame = cpFrame.getLink(); if (traceOn) logger.info("FAIL " + clause); continue main; } else if (state == StateFlag.SUSPEND) { // Require other generators to cycle before resuming this one preserveState(ccp); iContext.notifyBlockedOn(ccp); cpFrame = cpFrame.getLink(); if (traceOn)logger.info("SUSPEND " + clause); continue main; } pc = ccp.cpc; ac = ccp.cac; if (recordDerivations) { if (envFrame instanceof EnvironmentFrameWithDerivation) { ((EnvironmentFrameWithDerivation)envFrame).noteMatch(ccp.goal, pc); } } // then fall through to the execution context in which the the match was called } else { throw new ReasonerException("Internal error in backward rule system, unrecognized choice point"); } engine.incrementProfile(clause); interpreter: while (envFrame != null) { // Start of bytecode intepreter loop // Init the state variables pVars = envFrame.pVars; int yi, ai, ti; Node arg, constant; code = clause.getCode(); args = clause.getArgs(); while (true) { switch (code[pc++]) { case RuleClauseCode.TEST_BOUND: ai = code[pc++]; if (deref(argVars[ai]).isVariable()) { if (traceOn) logger.info("FAIL " + clause); continue main; } break; case RuleClauseCode.TEST_UNBOUND: ai = code[pc++]; if (! deref(argVars[ai]).isVariable()) { if (traceOn) logger.info("FAIL " + clause); continue main; } break; case RuleClauseCode.ALLOCATE: int envSize = code[pc++]; envFrame.allocate(envSize); pVars = envFrame.pVars; break; case RuleClauseCode.GET_VARIABLE : yi = code[pc++]; ai = code[pc++]; pVars[yi] = argVars[ai]; break; case RuleClauseCode.GET_TEMP : ti = code[pc++]; ai = code[pc++]; tVars[ti] = argVars[ai]; break; case RuleClauseCode.GET_CONSTANT : ai = code[pc++]; arg = argVars[ai]; if (arg instanceof Node_RuleVariable) arg = ((Node_RuleVariable)arg).deref(); constant = (Node) args[ac++]; if (arg instanceof Node_RuleVariable) { bind(arg, constant); } else { if (!arg.sameValueAs(constant)) { if (traceOn) logger.info("FAIL " + clause); continue main; } } break; case RuleClauseCode.GET_FUNCTOR: Functor func = (Functor)args[ac++]; boolean match = false; Node o = argVars[2]; if (o instanceof Node_RuleVariable) o = ((Node_RuleVariable)o).deref(); if (Functor.isFunctor(o)) { Functor funcArg = (Functor)o.getLiteralValue(); if (funcArg.getName().equals(func.getName())) { if (funcArg.getArgLength() == func.getArgLength()) { Node[] fargs = funcArg.getArgs(); for (int i = 0; i < fargs.length; i++) { argVars[i+3] = fargs[i]; } match = true; } } } else if (o.isVariable()) { // Construct a new functor in place Node[] fargs = new Node[func.getArgLength()]; Node[] templateArgs = func.getArgs(); for (int i = 0; i < fargs.length; i++) { Node template = templateArgs[i]; if (template.isVariable()) template = new Node_RuleVariable(null, i+3); fargs[i] = template; argVars[i+3] = template; } Node newFunc = Functor.makeFunctorNode(func.getName(), fargs); bind(((Node_RuleVariable)o).deref(), newFunc); match = true; } if (!match) { if (traceOn) logger.info("FAIL " + clause); continue main; // fail to unify functor shape } break; case RuleClauseCode.UNIFY_VARIABLE : yi = code[pc++]; ai = code[pc++]; if (!unify(argVars[ai], pVars[yi])) { if (traceOn) logger.info("FAIL " + clause); continue main; } break; case RuleClauseCode.UNIFY_TEMP : ti = code[pc++]; ai = code[pc++]; if (!unify(argVars[ai], tVars[ti])) { if (traceOn) logger.info("FAIL " + clause); continue main; } break; case RuleClauseCode.PUT_NEW_VARIABLE: yi = code[pc++]; ai = code[pc++]; argVars[ai] = pVars[yi] = new Node_RuleVariable(null, yi); break; case RuleClauseCode.PUT_VARIABLE: yi = code[pc++]; ai = code[pc++]; argVars[ai] = pVars[yi]; break; case RuleClauseCode.PUT_DEREF_VARIABLE: yi = code[pc++]; ai = code[pc++]; argVars[ai] = deref(pVars[yi]); break; case RuleClauseCode.PUT_TEMP: ti = code[pc++]; ai = code[pc++]; argVars[ai] = tVars[ti]; break; case RuleClauseCode.PUT_CONSTANT: ai = code[pc++]; argVars[ai] = (Node)args[ac++]; break; case RuleClauseCode.CLEAR_ARG: ai = code[pc++]; argVars[ai] = new Node_RuleVariable(null, ai); break; case RuleClauseCode.MAKE_FUNCTOR: Functor f = (Functor)args[ac++]; Node[] fargs = new Node[f.getArgLength()]; System.arraycopy(argVars, 3, fargs, 0, fargs.length); argVars[2] = Functor.makeFunctorNode(f.getName(), fargs); break; case RuleClauseCode.LAST_CALL_PREDICATE: // TODO: improved implementation of last call case case RuleClauseCode.CALL_PREDICATE: List<RuleClauseCode> clauses = ((RuleClauseCodeList) args[ac++]).getList(); // Check if this call is now grounded boolean groundCall = isGrounded(argVars[0]) && isGrounded(argVars[1]) && isGrounded(argVars[2]); setupClauseCall(pc, ac, clauses, groundCall); setupTripleMatchCall(pc, ac); continue main; case RuleClauseCode.CALL_PREDICATE_INDEX: // This code path is experimental, don't yet know if it has enough // performance benefit to justify the cost of maintaining it. clauses = ((RuleClauseCodeList) args[ac++]).getList(); // Check if we can futher index the clauses if (!argVars[2].isVariable()) { clauses = engine.getRuleStore().codeFor( new TriplePattern(argVars[0], argVars[1], argVars[2])); } setupClauseCall(pc, ac, clauses, false); setupTripleMatchCall(pc, ac); continue main; case RuleClauseCode.CALL_TRIPLE_MATCH: setupTripleMatchCall(pc, ac); continue main; case RuleClauseCode.CALL_TABLED: setupTabledCall(pc, ac); continue main; case RuleClauseCode.CALL_WILD_TABLED: Node predicate = deref(argVars[1]); if (engine.getRuleStore().isTabled(predicate)) { setupTabledCall(pc, ac); } else { // normal call set up clauses = engine.getRuleStore().codeFor( new TriplePattern(argVars[0], predicate, argVars[2])); if (clauses != null) setupClauseCall(pc, ac, clauses, false); setupTripleMatchCall(pc, ac); } continue main; case RuleClauseCode.PROCEED: pc = envFrame.cpc; ac = envFrame.cac; if (traceOn) logger.info("EXIT " + clause); if (choice != null) choice.noteSuccess(); if (recordDerivations && envFrame.getRule() != null) { if (envFrame instanceof EnvironmentFrameWithDerivation) { EnvironmentFrameWithDerivation efd = (EnvironmentFrameWithDerivation) envFrame; Triple result = efd.getResult(); List<Triple> matches = efd.getMatchList(); BackwardRuleInfGraphI infGraph = engine.getInfGraph(); RuleDerivation d = new RuleDerivation(envFrame.getRule(), result, matches, infGraph); infGraph.logDerivation(result, d); // Also want to record this result in the calling frame if (envFrame.link instanceof EnvironmentFrameWithDerivation) { EnvironmentFrameWithDerivation pefd = (EnvironmentFrameWithDerivation)envFrame.link; pefd.noteMatch(new TriplePattern(result), pc); } } } envFrame = (EnvironmentFrame) envFrame.link; if (envFrame != null) { clause = envFrame.clause; } continue interpreter; case RuleClauseCode.CALL_BUILTIN: Builtin builtin = (Builtin)args[ac++]; if (context == null) { BBRuleContext bbcontext = new BBRuleContext(engine.getInfGraph()); bbcontext.setEnv(new LPBindingEnvironment(this)); context = bbcontext; } context.setRule(clause.getRule()); if (!builtin.bodyCall(argVars, code[pc++], context)) { if (traceOn) logger.info("FAIL " + clause + ", due to " + builtin.getName()); continue main; } break; default : throw new ReasonerException("Internal error in backward rule system\nIllegal op code"); } } // End of innter code loop } // End of bytecode interpreter loop, gets to here if we complete an AND chain return StateFlag.ACTIVE; } // Gets to here if we have run out of choice point frames return StateFlag.FAIL; } /** * Tracing support - return a format set of triple queries/results. */ private String getArgTrace() { StringBuilder temp = new StringBuilder(); temp.append(PrintUtil.print(deref(argVars[0]))); temp.append(" "); temp.append(PrintUtil.print(deref(argVars[1]))); temp.append(" "); temp.append(PrintUtil.print(deref(argVars[2]))); return temp.toString(); } /** * Set up a triple match choice point as part of a CALL. */ private void setupTripleMatchCall(int pc, int ac) { TripleMatchFrame tmFrame = new TripleMatchFrame(this); tmFrame.setContinuation(pc, ac); tmFrame.linkTo(cpFrame); cpFrame = tmFrame; } /** * Set up a clause choice point as part of a CALL. */ private void setupClauseCall(int pc, int ac, List<RuleClauseCode> clauses, boolean isSingleton) { ChoicePointFrame newChoiceFrame = new ChoicePointFrame(this, clauses, isSingleton); newChoiceFrame.linkTo(cpFrame); newChoiceFrame.setContinuation(pc, ac); cpFrame = newChoiceFrame; } /** * Set up a tabled choice point as part of a CALL. */ private void setupTabledCall(int pc, int ac) { ConsumerChoicePointFrame ccp = new ConsumerChoicePointFrame(this); ccp.linkTo(cpFrame); ccp.setContinuation(pc, ac); cpFrame = ccp; } /** * Preserve the current interpter state in the consumer choice point at the top * of the choice point tree. */ public void preserveState(ConsumerChoicePointFrame ccp) { ccp.preserveState(trail); } /** * Restore the interpter state according to the given consumer choice point. */ public void restoreState(ConsumerChoicePointFrame ccp) { cpFrame = ccp; ccp.restoreState(this); iContext = ccp.context; } /** * Unify two nodes. Current implementation does not support functors. * @return true if the unifcation succeeds */ public boolean unify(Node n1, Node n2) { Node nv1 = n1; if (nv1 instanceof Node_RuleVariable) { nv1 = ((Node_RuleVariable)n1).deref(); } Node nv2 = n2; if (nv2 instanceof Node_RuleVariable) { nv2 = ((Node_RuleVariable)n2).deref(); } if (nv1 instanceof Node_RuleVariable) { bind(nv1, nv2); return true; } else if (nv2 instanceof Node_RuleVariable) { bind(nv2, nv1); return true; } else { return nv1.sameValueAs(nv2); } } /** * Bind a value to a variable, recording the binding in the trail. * @param var the dereferenced variable to be bound * @param val the value to bind to it */ public void bind(Node var, Node val) { ((Node_RuleVariable)var).simpleBind(val); trail.add(var); } /** * Unwind the trail to given low water mark */ public void unwindTrail(int mark) { for (int i = trail.size()-1; i >= mark; i--) { Node_RuleVariable var = (Node_RuleVariable)trail.get(i); var.unbind(); trail.remove(i); } } /** * Derefernce a node, following any binding trail. */ public static Node deref(Node node) { if (node instanceof Node_RuleVariable) { return ((Node_RuleVariable)node).deref(); } else { return node; } } /** * Check if a node values is now grounded */ public static boolean isGrounded(Node node) { return !( deref(node) instanceof Node_RuleVariable ); } /** * Return a dereferenced copy of a triple. */ public static Triple deref(TriplePattern t) { if (t == null) return null; return new Triple(deref(t.getSubject()), deref(t.getPredicate()), deref(t.getObject())); } /** * Derefernce a node which may be a functor node */ public static Node derefPossFunctor(Node node) { if (node instanceof Node_RuleVariable) { Node dnode = ((Node_RuleVariable)node).deref(); if (dnode.isVariable()) { // Problem with variable in return result "should never happen" throw new ReasonerException("Internal error in LP reasoner: variable in triple result"); } if (Functor.isFunctor(dnode)) { Functor f = (Functor) dnode.getLiteralValue(); Node[] fargs = f.getArgs(); boolean needCopy = false; for ( Node farg : fargs ) { if ( farg.isVariable() ) { needCopy = true; break; } } if (needCopy) { Node[] newArgs = new Node[fargs.length]; for (int i = 0; i < fargs.length; i++) { newArgs[i] = deref(fargs[i]); } dnode = Functor.makeFunctorNode(f.getName(), newArgs); } return dnode; } else { return dnode; } } else { return node; } } /** * Standardize a node by replacing instances of wildcard ANY by new distinct variables. * This is used in constructing the arguments to a top level call from a goal pattern. * @param node the node to be standardized * @param mappedVars known mappings from input variables to local variables */ private Node standardize(Node node, Map<Node, Node> mappedVars) { Node dnode = deref(node); if (node == Node.ANY || node == Node_RuleVariable.WILD) { return new Node_RuleVariable(null, 0); } else if (dnode.isVariable()) { Node mnode = mappedVars.get(dnode); if (mnode == null) { mnode = new Node_RuleVariable(null, 0); mappedVars.put(dnode, mnode); } return mnode; } else { return dnode; } } }