Skip to content

Commit b9a860c

Browse files
committed
FIXED: #1386 candidate deep list index preempting deterministic primary
For a small static predicate whose primary argument discriminates but whose non-primary compound argument shares a top-level functor across clauses, set_candidate_indexes() emits a list hash index on the compound argument. At call time existing_hash() matched that index (top-level functor bound), the deep drill entered a sub-list whose inner arguments were variables, and the recursion fell back to next_clause_unindexed(), leaving a spurious choice point on the non-matching clause. Hoist the "small predicate + bound primary key -> try primary first" block in first_clause_guarded() ahead of the hash lookup, and move the number_of_clauses == 0 shortcut with it (needed because clist->first_clause can still point to a stale erased ClauseRef when all clauses were retracted). When the primary key does not discriminate or is unbound, we fall through to the hash lookup unchanged, so p(X, (-1.0e+16, _)) still dispatches deterministically via the deep list index.
1 parent 2d6775b commit b9a860c

2 files changed

Lines changed: 44 additions & 28 deletions

File tree

src/pl-index.c

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,8 @@ first_clause_guarded(DECL_LD const Word argv, size_t argc, ClauseList clist,
647647
{ ClauseRef cref;
648648
ClauseIndex *cip;
649649
ClauseChoice chp = ctx->chp;
650+
iarg_t pindex;
651+
word pkey;
650652

651653
/* If `clist->unindexed`, no primary index is possible. */
652654

@@ -655,8 +657,40 @@ first_clause_guarded(DECL_LD const Word argv, size_t argc, ClauseList clist,
655657
return next_clause_unindexed(ctx);
656658
}
657659

658-
/* Deal with possible hashes */
659660
retry:
661+
cref = NULL;
662+
663+
if ( unlikely(clist->number_of_clauses == 0) )
664+
return NULL;
665+
666+
pindex = clist->primary_index;
667+
pkey = indexOfWord(argv[pindex]);
668+
669+
/* Fast path (#1386): at the outermost call, if the primary-index
670+
* argument is bound and the predicate is small, try the primary
671+
* index before probing candidate hash indexes. This prevents a
672+
* candidate list index whose shallow key does not discriminate
673+
* (e.g. all clauses share the same functor at the hashed arg)
674+
* from preempting a deterministic primary dispatch. When the
675+
* primary key does not discriminate (duplicate keys) or is not
676+
* available, we fall through to the hash lookup.
677+
*/
678+
679+
if ( pkey &&
680+
ctx->depth == 0 &&
681+
( clist->number_of_clauses <= MIN_CLAUSES_FOR_INDEX ||
682+
STATIC_RELOADING(ctx->predicate) ) )
683+
{ chp->key = pkey;
684+
chp->cref = clist->first_clause;
685+
cref = next_clause_primary_index(ctx);
686+
if ( !cref ||
687+
!(chp->cref && chp->cref->d.key == pkey &&
688+
cref->d.key == pkey) )
689+
return cref;
690+
/* else duplicate primary key: fall through to try hash */
691+
}
692+
693+
/* Deal with possible hashes */
660694
if ( (cip=clist->clause_indexes) )
661695
{ ClauseIndex best_index;
662696

@@ -700,8 +734,7 @@ first_clause_guarded(DECL_LD const Word argv, size_t argc, ClauseList clist,
700734
}
701735
}
702736

703-
iarg_t pindex = clist->primary_index;
704-
chp->key = indexOfWord(argv[pindex]);
737+
chp->key = pkey; /* existing_hash may have overwritten it */
705738

706739
if ( clist->fixed_indexes ) /* set_candidate_indexes() has been run */
707740
{ chp->cref = clist->first_clause;
@@ -711,37 +744,13 @@ first_clause_guarded(DECL_LD const Word argv, size_t argc, ClauseList clist,
711744
return next_clause_unindexed(ctx);
712745
}
713746

714-
if ( unlikely(clist->number_of_clauses == 0) )
715-
return NULL;
716-
717747
if ( isoff(ctx->predicate,
718748
P_DYNAMIC|P_MULTIFILE|P_THREAD_LOCAL|P_FOREIGN) &&
719749
ctx->depth == 0 )
720750
{ set_candidate_indexes(ctx->predicate, clist, 10, true);
721751
goto retry;
722752
}
723753

724-
/* Try the primary index if the corresponding argument is bound and
725-
* we have less than MIN_CLAUSES_FOR_INDEX clauses. Accept if we
726-
* have no clause or the next candidate has a different key. If the
727-
* next candidate has the same key, deep indexing may help us, so we
728-
* will search for other indexes.
729-
*/
730-
731-
if ( chp->key &&
732-
( clist->number_of_clauses <= MIN_CLAUSES_FOR_INDEX ||
733-
STATIC_RELOADING(ctx->predicate)) )
734-
{ chp->cref = clist->first_clause;
735-
cref = next_clause_primary_index(ctx);
736-
if ( !cref ||
737-
!(chp->cref && chp->cref->d.key == chp->key &&
738-
cref->d.key == chp->key) )
739-
return cref;
740-
/* else duplicate; see whether we can create a deep index */
741-
/* TBD: Avoid trying this every goal */
742-
} else
743-
cref = NULL;
744-
745754
if ( !STATIC_RELOADING(ctx->predicate) )
746755
{ ClauseIndex ci;
747756

@@ -757,7 +766,7 @@ first_clause_guarded(DECL_LD const Word argv, size_t argc, ClauseList clist,
757766
}
758767
}
759768

760-
if ( cref ) /* from next_clause_primary_index() call */
769+
if ( cref ) /* from primary-first fast-path fallthrough */
761770
return cref;
762771

763772
chp->cref = clist->first_clause;

tests/db/test_jit.pl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,9 @@
212212
pa(_, _).
213213
pa(_, x).
214214

215+
ix1386(real, (-1.0e+16, 1.0e+16)).
216+
ix1386(boolean, (0, 1)).
217+
215218
test(x) :- % must use S_LIST
216219
x(_,_,_,_,[]).
217220
test(a) :- % must use S_LIST (test H_VOID_N)
@@ -226,5 +229,9 @@
226229
!.
227230
test(pa) :- % primary index should be on arg 2
228231
pa(_,y).
232+
test(ix1386) :- % Issue #1386: primary must win over
233+
call_cleanup(ix1386(real, (_,_)), % a shallow-useless deep list index
234+
Det=true),
235+
assertion(Det == true).
229236

230237
:- end_tests(jit_static).

0 commit comments

Comments
 (0)