It looks like the causes for our two examples were different, but the following will backport both changes into 6M62:
RelationKind.i6t: Hash Core Relation Handler
Include (-
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! RelationKind.i6t: Hash Core Relation Handler (MODIFIED)
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! MODIFIED
[ HashCoreRelationHandler rel task kx ky X Y mult sym rev at tmp fl;
if (task == RELS_SET_VALENCY) {
return RELATION_TY_SetValency(rel, X);
} else if (task == RELS_DESTROY) {
! clear
kx = KOVIsBlockValue(kx); ky = KOVIsBlockValue(ky);
if (~~(kx || ky)) return;
at = BlkValueRead(rel, RRV_STORAGE);
while (at >= 0) {
fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at);
if (fl & RRF_USED) {
if (kx) BlkValueFree(BlkValueRead(rel, RRV_DATA_BASE + 3*at + 1));
if (ky || ~~(fl & RRF_SINGLE))
BlkValueFree(BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2));
}
at--;
}
return;
} else if (task == RELS_COPY) {
X = KOVIsBlockValue(kx); Y = KOVIsBlockValue(ky);
if (~~(X || Y)) return;
at = BlkValueRead(rel, RRV_STORAGE);
while (at >= 0) {
fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at);
if (fl & RRF_USED) {
if (X) {
tmp = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 1);
tmp = BlkValueCopy(BlkValueCreate(kx), tmp);
BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 1, tmp);
}
if (Y || ~~(fl & RRF_SINGLE)) {
tmp = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2);
tmp = BlkValueCopy(BlkValueCreate(BlkValueWeakKind(tmp)), tmp);
BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, tmp);
}
}
at--;
}
return;
} else if (task == RELS_SHOW) {
print (string) BlkValueRead(rel, RRV_DESCRIPTION), ":^";
! Z-machine doesn't have the room to let us pass sym/rev as parameters
switch (RELATION_TY_GetValency(rel)) {
RRVAL_SYM_V_TO_V:
sym = 1;
tmp = KOVComparisonFunction(kx);
if (~~tmp) tmp = UnsignedCompare;
RRVAL_O_TO_V:
rev = 1;
}
for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at);
if (fl & RRF_USED) {
X = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 1);
Y = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2);
if (fl & RRF_SINGLE) {
if (sym && tmp(X, Y) > 0) continue;
print " ";
if (rev) PrintKindValuePair(ky, Y);
else PrintKindValuePair(kx, X);
if (sym) print " <=> "; else print " >=> ";
if (rev) PrintKindValuePair(kx, X);
else PrintKindValuePair(ky, Y);
print "^";
} else {
for (mult=1: mult<=LIST_OF_TY_GetLength(Y): mult++) {
fl = LIST_OF_TY_GetItem(Y, mult);
if (sym && tmp(X, fl) > 0) continue;
print " ";
if (rev) PrintKindValuePair(ky, fl);
else PrintKindValuePair(kx, X);
if (sym) print " <=> "; else print " >=> ";
if (rev) PrintKindValuePair(kx, X);
else PrintKindValuePair(ky, fl);
print "^";
}
}
}
}
return;
} else if (task == RELS_EMPTY) {
if (BlkValueRead(rel, RRV_USED) == 0) rtrue;
if (X == 1) {
HashCoreRelationHandler(rel, RELS_DESTROY);
for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
tmp = RRV_DATA_BASE + 3*at;
BlkValueWrite(rel, tmp, 0);
BlkValueWrite(rel, tmp + 1, 0);
BlkValueWrite(rel, tmp + 2, 0);
}
BlkValueWrite(rel, RRV_USED, 0);
BlkValueWrite(rel, RRV_FILLED, 0);
rtrue;
}
rfalse;
} else if (task == RELS_LOOKUP_ANY) {
if (Y == RLANY_GET_Y or RLANY_CAN_GET_Y) {
at = HashCoreLookUp(rel, kx, X);
if (at >= 0) {
if (Y == RLANY_CAN_GET_Y) rtrue;
tmp = RRV_DATA_BASE + 3*at;
fl = BlkValueRead(rel, tmp);
tmp = BlkValueRead(rel, tmp + 2);
if (fl & RRF_SINGLE) return tmp;
return LIST_OF_TY_GetItem(tmp, 1);
}
} else {
for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
tmp = RRV_DATA_BASE + 3*at;
fl = BlkValueRead(rel, tmp);
if (fl & RRF_USED) {
sym = BlkValueRead(rel, tmp + 2);
if (fl & RRF_SINGLE) {
if (KOVIsBlockValue(ky)) {
if (BlkValueCompare(X, sym) ~= 0) continue;
} else {
if (X ~= sym) continue;
}
} else {
if (LIST_OF_TY_FindItem(sym, X) == 0) continue;
}
if (Y == RLANY_CAN_GET_X) rtrue;
return BlkValueRead(rel, tmp + 1);
}
}
}
if (Y == RLANY_GET_X or RLANY_GET_Y)
print "*** Lookup failed: value not found ***^";
rfalse;
} else if (task == RELS_LOOKUP_ALL_X) {
if (BlkValueWeakKind(Y) ~= LIST_OF_TY) rfalse;
LIST_OF_TY_SetLength(Y, 0);
for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
tmp = RRV_DATA_BASE + 3*at;
fl = BlkValueRead(rel, tmp);
if (fl & RRF_USED) {
sym = BlkValueRead(rel, tmp + 2);
if (fl & RRF_SINGLE) {
if (KOVIsBlockValue(kx)) { ! MODIFIED per https://github.com/ganelson/inform/commit/7407443ae76d5f7690c047797b1dcbfc1e96e8b8#diff-0e165205db1b430fae941a6a30ccb212632ae4cbdbf056fefd4d5d788f3142b1
if (BlkValueCompare(X, sym) ~= 0) continue;
} else {
if (X ~= sym) continue;
}
} else {
if (LIST_OF_TY_FindItem(sym, X) == 0) continue;
}
LIST_OF_TY_InsertItem(Y, BlkValueRead(rel, tmp + 1));
}
}
return Y;
} else if (task == RELS_LOOKUP_ALL_Y) {
if (BlkValueWeakKind(Y) ~= LIST_OF_TY) rfalse;
LIST_OF_TY_SetLength(Y, 0);
at = HashCoreLookUp(rel, kx, X);
if (at >= 0) {
tmp = RRV_DATA_BASE + 3*at;
fl = BlkValueRead(rel, tmp);
tmp = BlkValueRead(rel, tmp + 2);
if (fl & RRF_SINGLE)
LIST_OF_TY_InsertItem(Y, tmp);
else
LIST_OF_TY_AppendList(Y, tmp);
}
return Y;
} else if (task == RELS_LIST) {
if (BlkValueWeakKind(X) ~= LIST_OF_TY) rfalse;
LIST_OF_TY_SetLength(X, 0);
switch (Y) {
RLIST_ALL_X:
for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
tmp = RRV_DATA_BASE + 3*at;
fl = BlkValueRead(rel, tmp);
if (fl & RRF_USED)
LIST_OF_TY_InsertItem(X, BlkValueRead(rel, tmp + 1));
}
return X;
RLIST_ALL_Y:
for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
tmp = RRV_DATA_BASE + 3*at;
fl = BlkValueRead(rel, tmp);
if (fl & RRF_USED) {
tmp = BlkValueRead(rel, tmp + 2);
if (fl & RRF_SINGLE)
LIST_OF_TY_InsertItem(X, tmp, false, 0, true);
else
LIST_OF_TY_AppendList(X, tmp, false, 0, true);
}
}
return X;
RLIST_ALL_PAIRS:
if (RELATION_TY_GetValency(rel) == RRVAL_O_TO_V) rev = 1;
! LIST_OF_TY_InsertItem will make a deep copy of the item,
! so we can reuse a single combination value here
Y = BlkValueCreate(COMBINATION_TY, tmp);
for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
tmp = RRV_DATA_BASE + 3*at;
fl = BlkValueRead(rel, tmp);
if (fl & RRF_USED) {
BlkValueWrite(Y, COMBINATION_ITEM_BASE + rev, BlkValueRead(rel, tmp + 1));
tmp = BlkValueRead(rel, tmp + 2);
if (fl & RRF_SINGLE) {
BlkValueWrite(Y, COMBINATION_ITEM_BASE + 1 - rev, tmp);
LIST_OF_TY_InsertItem(X, Y);
} else {
for ( mult = LIST_OF_TY_GetLength(tmp): mult > 0: mult-- ) {
BlkValueWrite(Y, COMBINATION_ITEM_BASE + 1 - rev,
LIST_OF_TY_GetItem(tmp, mult));
LIST_OF_TY_InsertItem(X, Y);
}
}
}
}
BlkValueWrite(Y, COMBINATION_ITEM_BASE, 0);
BlkValueWrite(Y, COMBINATION_ITEM_BASE + 1, 0);
BlkValueFree(Y);
return X;
}
rfalse;
}
at = HashCoreLookUp(rel, kx, X);
switch(task) {
RELS_TEST:
if (at < 0) rfalse;
fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at);
tmp = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2);
if (fl & RRF_SINGLE) {
if (KOVIsBlockValue(ky)) {
if (BlkValueCompare(tmp, Y) == 0) rtrue;
} else {
if (tmp == Y) rtrue;
}
rfalse;
} else {
return LIST_OF_TY_FindItem(tmp, Y);
}
RELS_ASSERT_TRUE:
if (at < 0) {
! no entry exists for this key, just add one
at = ~at;
BlkValueWrite(rel, RRV_USED, BlkValueRead(rel, RRV_USED) + 1);
if (BlkValueRead(rel, RRV_DATA_BASE + 3*at) == 0)
BlkValueWrite(rel, RRV_FILLED, BlkValueRead(rel, RRV_FILLED) + 1);
BlkValueWrite(rel, RRV_DATA_BASE + 3*at, RRF_USED+RRF_SINGLE);
if (KOVIsBlockValue(kx)) { X = BlkValueCopy(BlkValueCreate(kx), X); }
if (KOVIsBlockValue(ky)) { Y = BlkValueCopy(BlkValueCreate(ky), Y); }
BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 1, X);
BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, Y);
HashCoreCheckResize(rel);
break;
}
! an entry exists: could be a list or a single value
fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at); ! flags
tmp = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2); ! value or list
if (fl & RRF_SINGLE) {
! if Y is the same as the stored key, we have nothing to do
if (KOVIsBlockValue(ky)) {
if (BlkValueCompare(tmp, Y) == 0) rtrue;
} else {
if (tmp == Y) rtrue;
}
! it's different: either replace it or expand into a list,
! depending on the value of mult
if (mult) {
fl = BlkValueCreate(LIST_OF_TY); ! new list
BlkValueWrite(fl, LIST_ITEM_KOV_F, ky);
LIST_OF_TY_SetLength(fl, 2);
BlkValueWrite(fl, LIST_ITEM_BASE, tmp); ! do not copy
LIST_OF_TY_PutItem(fl, 2, Y); ! copy if needed
BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, fl);
BlkValueWrite(rel, RRV_DATA_BASE + 3*at, RRF_USED);
} else {
if (KOVIsBlockValue(ky)) {
BlkValueFree(tmp);
Y = BlkValueCopy(BlkValueCreate(ky), Y);
}
BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, Y);
}
} else {
! if Y is present already, do nothing. otherwise add it.
LIST_OF_TY_InsertItem(tmp, Y, 0, 0, 1);
}
rtrue;
RELS_ASSERT_FALSE:
if (at < 0) rtrue;
! an entry exists: could be a list or a single value
fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at); ! flags
tmp = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2); ! value or list
if (fl & RRF_SINGLE) {
! if the stored key isn't Y, we have nothing to do
if (KOVIsBlockValue(ky)) {
if (BlkValueCompare(tmp, Y) ~= 0) rtrue;
} else {
if (tmp ~= Y) rtrue;
}
! delete the entry
if (KOVIsBlockValue(ky))
BlkValueFree(BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2));
.DeleteEntryIgnoringY;
BlkValueWrite(rel, RRV_USED, BlkValueRead(rel, RRV_USED) - 1);
if (KOVIsBlockValue(kx))
BlkValueFree(BlkValueRead(rel, RRV_DATA_BASE + 3*at + 1));
BlkValueWrite(rel, RRV_DATA_BASE + 3*at, RRF_DELETED);
BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 1, 0);
BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, 0);
} else {
! remove Y from the list if present
LIST_OF_TY_RemoveValue(tmp, Y, 1);
! if the list is now empty, delete the whole entry
if (LIST_OF_TY_GetLength(tmp) == 0) {
BlkValueFree(tmp);
jump DeleteEntryIgnoringY;
}
}
rtrue;
}
rtrue;
];
[ HashCoreLookUp rel kx X hashv i free mask perturb flags;
!print "[HCLU rel=", rel, " kx=", kx, " X=", X, ": ";
! calculate a hash value for the key
hashv = GetHashValue(kx, x);
! look in the first expected slot
mask = BlkValueRead(rel, RRV_STORAGE);
i = hashv & mask;
!print "hv=", hashv, ", trying ", i;
flags = BlkValueRead(rel, RRV_DATA_BASE + 3*i);
if (flags == 0) {
!print " - not found]^";
return ~i;
}
if (HashCoreEntryMatches(rel, i, kx, X)) {
!print " - found]^";
return i;
}
! not here, keep looking in sequence
free = -1;
if (flags & RRF_DELETED) free = i;
perturb = hashv;
hashv = i;
for (::) {
hashv = hashv*5 + perturb + 1;
i = hashv & mask;
!print ", ", i;
flags = BlkValueRead(rel, RRV_DATA_BASE + 3*i);
if (flags == 0) {
!print " - not found]^";
if (free >= 0) return ~free;
return ~i;
}
if (HashCoreEntryMatches(rel, i, kx, X)) {
!print " - found]^";
return i;
}
if ((free < 0) && (flags & RRF_DELETED)) free = i;
#ifdef TARGET_ZCODE;
@log_shift perturb (-RRP_PERTURB_SHIFT) -> perturb;
#ifnot;
@ushiftr perturb RRP_PERTURB_SHIFT perturb;
#endif;
}
];
! MODIFIED
[ HashCoreCheckResize rel filled ext newext temp i at kov kx F X Y;
filled = BlkValueRead(rel, RRV_FILLED);
ext = BlkValueRead(rel, RRV_STORAGE) + 1;
if (filled >= (ext - filled) * RRP_CROWDED_IS) {
! copy entries to temporary space
temp = FlexAllocate(ext * (3*WORDSIZE), TEXT_TY, BLK_FLAG_WORD+BLK_FLAG_MULTIPLE);
for (i=0: i<ext*3: i++)
BlkValueWrite(temp, i, BlkValueRead(rel, RRV_DATA_BASE+i), true);
! resize and clear our data
if (ext >= RRP_LARGE_IS) newext = ext * RRP_RESIZE_LARGE;
else newext = ext * RRP_RESIZE_SMALL;
BlkValueSetLBCapacity(rel, RRV_DATA_BASE + newext*3);
BlkValueWrite(rel, RRV_STORAGE, newext - 1);
BlkValueWrite(rel, RRV_FILLED, BlkValueRead(rel, RRV_USED));
for (i=0: i<newext*3: i++)
BlkValueWrite(rel, RRV_DATA_BASE+i, 0);
! copy entries back from temporary space
kov = BlkValueRead(rel, RRV_KIND);
kx = KindBaseTerm(kov, 1); ! MODIFIED per https://github.com/ganelson/inform/commit/9bffe2a889837879e8be317fe076b75ced746450
for (i=0: i<ext: i++) {
F = BlkValueRead(temp, 3*i, true);
if (F == 0 || (F & RRF_DELETED)) continue;
X = BlkValueRead(temp, 3*i + 1, true);
Y = BlkValueRead(temp, 3*i + 2, true);
at = HashCoreLookUp(rel, kx, X);
if (at >= 0) { print "*** Duplicate entry while resizing ***^"; rfalse; }
at = ~at;
BlkValueWrite(rel, RRV_DATA_BASE + 3*at, F);
BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 1, X);
BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, Y);
}
! done with temporary space
FlexFree(temp);
}
];
[ HashCoreEntryMatches rel at kx X cx cy;
cx = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 1);
if (KOVIsBlockValue(kx)) {
if (BlkValueCompare(cx, X) ~= 0) rfalse;
} else {
if (cx ~= X) rfalse;
}
rtrue;
];
-) instead of "Hash Core Relation Handler" in "RelationKind.i6t".