Skip to content

Instantly share code, notes, and snippets.

@lemmy
Created February 18, 2019 23:59
Show Gist options
  • Save lemmy/1eaf4bec8910b25e206d070b0bc80754 to your computer and use it in GitHub Desktop.
Save lemmy/1eaf4bec8910b25e206d070b0bc80754 to your computer and use it in GitHub Desktop.
TLC module overwrites OpenAddressing spec
import tlc2.value.BoolValue;
import tlc2.value.FcnRcdValue;
import tlc2.value.IntValue;
import tlc2.value.IntervalValue;
import tlc2.value.ModelValue;
import tlc2.value.Value;
public class OpenAddressing {
private static final int minT = 1;
public static IntValue shiftR(final IntValue n, final IntValue pos) {
return IntValue.gen(n.val >>> pos.val);
}
// rescale(k,maxF,minF,fp,p)
public static IntValue rescale(final IntValue k, final IntValue maxF, final IntValue minF, final IntValue fp,
final IntValue p) {
return IntValue.gen(rescale(k.val, maxF.val, minF.val, fp.val, p.val));
}
static int rescale(final int maxT, final int maxF, final int minF, final int fp) {
return rescale(maxT, maxF, minF, fp, 0);
}
static int rescale(final int maxT, final int maxF, final int minF, final int fp, final int probe) {
final float factor = (maxT - minT) / ((maxF - minF) * 1f);
int idx = Math.round(factor * (fp - minF) + minT) + probe;
// Modulo might return an index of maxT.
while (idx > maxT) {
idx = idx % (maxT + 1) + 1;
}
return idx;
}
public static Value noDupes(final IntervalValue fps, final FcnRcdValue frv, final ModelValue exclude) {
final boolean[] arr = new boolean[fps.size()];
for (final Value value : frv.values) {
if (value != exclude) {
final int val = Math.abs(((IntValue) value).val);
if (arr[val - fps.low] == true) {
return BoolValue.ValFalse;
}
arr[val - fps.low] = true;
}
}
return BoolValue.ValTrue;
}
}
\begin{ppcal}
-------------------------- MODULE OpenAddressing --------------------------
EXTENDS Sequences, FiniteSets, Integers, TLC
(***************************************************************************)
(* K: The overall number of fingerprints that fit into the table. *)
(* fps: The set of fingerprints to be inserted into the hash table. *)
(* empty: An empty (model) value. Used to mark an unoccupied table element.*)
(* Writer: The set of processes/threads which insert fingerprints. Reader: *)
(* The set of processes which check fingerprints with contains. L: The *)
(* probing limit. *)
(***************************************************************************)
CONSTANT K, fps, empty, Writer, L, MAX, MIN\*, Reader
(***************************************************************************)
(* K is a positive natural. emtpy is different from all elements in fps. *)
(* fingerprints are natural numbers and can be well-ordered. *)
(***************************************************************************)
ASSUME /\ K \in (Nat \ {0})
/\ \A fp \in fps: fp \in (Nat \ {0})
/\ empty \notin fps
/\ (2*L) <= K
----------------------------------------------------------------------------
(***************************************************************************)
(* The image of the function F. *)
(***************************************************************************)
Image(F) == { F[x] : x \in DOMAIN F }
(***************************************************************************)
(* The element of position Len(seq) of a sequence seq. *)
(***************************************************************************)
last(seq) == seq[Len(seq)]
(***************************************************************************)
(* The largest element in the sequence, assuming sequence to be sorted in *)
(* ascending order. *)
(***************************************************************************)
largestElem(sortedSeq) == IF sortedSeq = <<>> THEN 0 ELSE last(sortedSeq)
(***************************************************************************)
(* All elements of seq1 smaller than elem and the largest element in seq2. *)
(***************************************************************************)
subSeqSmaller(seq1, seq2, elem) == SelectSeq(seq1, LAMBDA p:
p < elem /\ p > largestElem(seq2))
(***************************************************************************)
(* All elements of seq1 larger than the largest element in seq2. *)
(***************************************************************************)
subSeqLarger(seq1, seq2) == IF seq2 = <<>>
THEN seq1
ELSE SelectSeq(seq1, LAMBDA p:
p > largestElem(seq2))
(***************************************************************************)
(* TRUE iff the sequence seq contains the element elem. *)
(***************************************************************************)
containsElem(seq, elem) == elem \in Image(seq)
(***************************************************************************)
(* The minimum and maximum element in set S. *)
(***************************************************************************)
\*min(S) == CHOOSE s \in S: \A a \in S: s <= a
\*max(S) == CHOOSE s \in S: \A a \in S: s >= a
(***************************************************************************)
(* The smaller of the two values. *)
(***************************************************************************)
minimum(a, b) == IF a < b THEN a ELSE b
(***************************************************************************)
(* The given index i modulo the sequences' length. *)
(***************************************************************************)
mod(i,len) == IF i % len = 0 THEN len ELSE (i % len)
(***************************************************************************)
(* Logical bit-shifting to the right (shifts in zeros from the left/MSB). *)
(* TLC's standard division does not round towards zero, thus this is *)
(* specified recursively, manually taking care of rounding. *)
(***************************************************************************)
RECURSIVE shiftR(_,_)
shiftR(n,pos) == IF pos = 0 THEN n
ELSE LET odd(z) == z % 2 = 1
m == IF odd(n) THEN (n-1) \div 2 ELSE n \div 2
IN shiftR(m, pos - 1)
(***************************************************************************)
(* Bitshifting (faster for any real implementation). *)
(***************************************************************************)
bitshift(fp, p) == LET k == CHOOSE k \in 1..K: 2^k = K
IN mod(shiftR(fp, k - 1) + 1 + p, K)
(***************************************************************************)
(* Re-scale. *)
(***************************************************************************)
rescale(k,maxF,minF,fp,p) == LET f == (k - 1) \div (maxF - minF)
IN mod((f * (fp - minF + 1)) + p, k)
(***************************************************************************)
(* Calculates an fp's index where fp \in fps. p is an alternative address, *)
(* such that: p \in 0..P. Uses bitshifting iff K is power of two. *)
(***************************************************************************)
ASSUME(TLCSet(42, <<"idx", 0>>))
isPowerOfTwo == \E n \in 1..K: 2^n = K
idx(fp, p) == IF isPowerOfTwo
THEN bitshift(fp, p)
ELSE rescale(K, MAX, MIN, fp, p)
(***************************************************************************)
(* TRUE iff the fingerprint at table position index is equal to fp or its *)
(* corresponding negative fp value (marked as to be copied to external). *)
(***************************************************************************)
isMatch(fp, index, table) == \/ table[index] = fp
\/ table[index] = (-1*fp)
(***************************************************************************)
(* TRUE iff the table at position index is empty. *)
(***************************************************************************)
isEmpty(index, table) == table[index] = empty
(***************************************************************************)
(* TRUE iff the table at position index is marked evicted. *)
(***************************************************************************)
isMarked(index, table) == table[index] < 0
----------------------------------------------------------------------------
(***************************************************************************)
(* A fp wrapped around if its alternate indices are beyond K and its *)
(* actual index is lower than its primary idx. Another mental picture for *)
(* table is a circular list and wrapped means that a fingerprint crossed *)
(* the logically first position of table. *)
(***************************************************************************)
wrapped(fp, pos) == idx(fp, 0) > mod(pos, K)
(***************************************************************************)
(* Compare the two fingerprints fp1 and fp2 for order with regards to *)
(* their numerical values and their respective positions i1 and i2. *)
(* *)
(* Returns -1, iff fp2 is less than fp1. Returns 0, iff fp1 and fp2 are *)
(* equal. Returns 1, iff fp1 is less than fp2. *)
(* *)
(* compare considers three cases: *)
(* 1) Iff either one or both fingerprints are empty, they are defined to *)
(* be equal. Under the assumption of a stable sorting algorithm, fp1 *)
(* and fp2 are not swapped (ELSE 0). *)
(* 2) Iff neither one or both fingerprints wrapped, a basic comparison is *)
(* done. A basic comparison is one, where the lower positioned fp has *)
(* to be numerically lower. *)
(* 3) Iff the truth values for wrapped differed, two cases have to be *)
(* distinguished: *)
(* *)
(* Let: *)
(* $\overset{\circlearrowleft}{fps} \triangleq {fp \in fps: *)
(* \E i \in Image(PS[fp]): wrapped(fp,i)}$ *)
(* *)
(* $\overrightarrow{fps} \triangleq fps \ *)
(* \overset{\circlearrowleft}{fps}$ *)
(* *)
(* 3a) Comparison when fp1 and fp2 are both in *)
(* $\overset{\circlearrowleft}{fps}$. If fp1 is at a lower *)
(* position (thus wrapped) and numerically lower, swap it with fp2 *)
(* which is at a higher position and thus did not wrap. *)
(* For example, fp1 was inserted into the table after fp2 and thus *)
(* wrapped, but is numerically lower than fp2. *)
(* *)
(* 3b) Special case comparison required by Insertion Sort. It compares a *)
(* fingerprint in $\overset{\circlearrowleft}{fps}$ with one in *)
(* $\overrightarrow{fps}$. *)
(* Insertion Sort compares adjacent elements. Thus, without this case *)
(* two fingerprints fp1 and fpX, which are eventually handled by 3a), *)
(* would not be sorted, iff fp2 is inbetween of fp1 and fpX. Thus, fp1 *)
(* is swapped with fp2 meaning it moves towards the beginning of table.*)
(* Eventually, all wrapped fingerprints in *)
(* $\overset{\circlearrowleft}{fps}$ will form a cluster *)
(* at the beginning of t and can then be sorted with 3a). In other *)
(* words, we allow the wrapped fingerprints to be compacted at the *)
(* beginning ot the table and non-wrapping fingerprints to be moved to *)
(* higher positions. *)
(* *)
(* Assumeming that the beginning of table is: *)
(* <<1,23,22,...,24>> (assuming fps is 1..24, L=3 and K=6. Sorted, *)
(* table needs to change to <<1,23,24,...,22>>. *)
(* Without 3b), Insertion Sort compares 22 to 1 and 23 to 22. The *)
(* outcome would be <<1,22,23,...,24>>, which is cleary not fully *)
(* sorted. Thus, in order to handle this case, we allow IS to swap 22 *)
(* and 23 with 1. As a result, table - when sorted - is *)
(* <<23,24,1,...,22>>. *)
(* *)
(* Can we be sure, that non-wrapping fingerprints do not get moved out *)
(* beyond the end of their probing sequence? *)
(* Obvously, at most, L-1 wrapping fingerprints can be located at the *)
(* beginning of table. In this case, only one non-wrapping fingerprint *)
(* will be in the table, which maximally will be moved L-1 positions *)
(* to the right with regards to its primary position. *)
(***************************************************************************)
compare(fp1,i1,fp2,i2) ==
IF fp1 # empty /\ fp2 # empty \* 1)
THEN IF wrapped(fp1, i1) = wrapped(fp2, i2) \* 2)
THEN IF i1 > i2 /\ fp1 < fp2 THEN -1 ELSE 1
ELSE IF i1 < i2 /\ fp1 < fp2 THEN -1 ELSE \* 3a
IF i1 > i2 /\ fp1 > fp2 THEN -1 ELSE 0 \* 3b
ELSE 0
----------------------------------------------------------------------------
(*** this is a comment containing the PlusCal code *
--algorithm OpenAddressing
(***************************************************************************)
(* table: The actual hash table specified as a TLA+ sequence. history: An *)
(* auxiliary (history) variable unrelated to the actual hash table *)
(* specification. It just records the inserted fingerprints to be verified *)
(* by Inv. An implementation won't need history. external: The external *)
(* storage where fingerprints are eventually evicted to. outer/inner: *)
(* Index variables local to the sort action. P: The number of times an *)
(* alterante index is to be tried. *)
(***************************************************************************)
{ variable table = [i \in 1..K |-> empty],
external = <<>>,
newexternal = <<>>,
evict = FALSE, \* AtomicBoolean in Java
waitCnt = 0, \* CyclicBarrier in Java
history = {};
(************************************************************************)
(* Compare-and-swap is linearizable (appears atomic from the POV of *)
(* observers such as other threads) and disjoint-access-parallel (two *)
(* CAS operations on disjoint memory locations do not synchronize). *)
(* *)
(* Variants of CAS (only basic CAS supported by Java. No HW support *)
(* for DCAS, CASN): *)
(* CAS: Basic compare and swap of a 64bit memory location. *)
(* DWCAS: CAS of two adjacent/contiguous 64bit memory locations. *)
(* (CMPXCHG16) (to swap to adjacent positions in e.g table) *)
(* DCAS: CAS of two arbitrary 64bit memory locations (swap of arbitrary *)
(* locations). *)
(* CASN: CAS N arbitrary 64bit memory locations. *)
(* *)
(* http://liblfds.org/mediawiki/index.php?title=Article:CAS_and_LL/ *)
(* SC_Implementation_Details_by_Processor_family *)
(************************************************************************)
(* Atomically compare and swap an element of table. *)
(* Atomicity is implicit due to the absence of labels. *)
macro CAS(result, pos, expected, new) {
if (table[pos] = expected) {
table[pos] := new;
result := TRUE
} else {
result := FALSE
}
}
procedure Evict()
variables ei = 1, ej = 1, lo = 0; {
(* Insertion sort. *)
strIns: while (ei <= K+L) {
lo := table[mod(ei + 1, K)];
nestedIns: while (compare(lo, mod(ei + 1, K),
table[mod(ej, K)], mod(ej, K)) <= -1) {
table[mod(ej + 1, K)] := table[mod(ej, K)];
if (ej = 0) {
ej := ej - 1;
goto set;
} else {
ej := ej - 1;
};
};
set: table[mod(ej + 1, K)] := lo;
ej := ei + 1;
ei := ei + 1;
};
ei := 1;
(* Write to external storage. *)
flush: while (ei <= K+L) {
lo := table[mod(ei, K)];
if (lo # empty /\
lo > largestElem(newexternal) /\
((ei <= K /\ ~wrapped(lo,ei)) \/
(ei > K /\ wrapped(lo,ei)))) {
(* Copy all smaller fps than lo from *)
(* secondary to newexternal. *)
newexternal := Append(newexternal \o
subSeqSmaller(external, newexternal, lo), lo);
(* Mark table[mod(cpy,table)] as being *)
(* written to external. *)
table[mod(ei, K)] := lo * (-1);
};
ei := ei + 1;
};
(* Append remainder of external to newexternal and *)
(* assign newexternal to external. *)
external := newexternal \o
subSeqLarger(external, newexternal);
newexternal := <<>>;
rtrn: return;
}
\* process (q \in Reader)
\* variables rfp = 0, rindex = 0, checked = {}; {
\* rwait: await history # {};
\* rpick: while (TRUE) {
\* if (checked = history /\ history = fps) {
\* goto Done;
\* } else {
\* with (f \in history) {
\* rfp := f;
\* checked := checked \cup {f};
\* };
\* };
\*
\* rcntns: rindex := 0;
\* if (evict) {
\* waitCnt := waitCnt + 1;
\* rwaitEv: await evict = FALSE;
\* rendWEv: waitCnt := waitCnt - 1;
\* goto rcntns
\* };
\*
\* ronPrm: while (rindex <= L) {
\* if (isMatch(rfp, idx(rfp, rindex), table)) {
\* goto rpick
\* } else {
\* if (isEmpty(idx(rfp, rindex), table)
\* \/ isMarked(idx(rfp, rindex), table)) {
\* goto ronSnc;
\* } else {
\* rindex := rindex + 1
\* }
\* }
\* };
\* ronSnc: if (containsElem(external,rfp)) {
\* goto rpick
\* } else {
\* (* Since we picked a fp from history, it always *)
\* (* either has to be in table or external. *)
\* assert(FALSE);
\* };
\* }
\* }
(* A weak fair process. *)
fair process (p \in Writer)
variables fp = 0, index = 0, result = FALSE, expected = -1; {
pick: while (TRUE) {
(* No deadlock once all fingerprints have been inserted. *)
if ((fps \ history) = {}) {
goto Done;
} else {
(* Select some fp to be inserted *)
with (f \in (fps \ history)) { fp := f; };
};
put: index := 0;
result := FALSE;
(* Set expected to infinity. expected is reused when *)
(* the algorithm runs a primary lookup and finds a *)
(* position which is either EMPTY or isMarked(...). *)
(* expected stores the (open) position for later use *)
(* where the fp is inserted. Maximally, a position *)
(* can be K + L, thus expected is set to K + L + 1; *)
(* as an approximation of infinity. *)
expected := L;
(* Wait for eviction thread to do its work. *)
if (evict) {
waitCnt := waitCnt + 1;
waitEv: await evict = FALSE;
endWEv: waitCnt := waitCnt - 1;
goto put
};
(* Check external unless empty. First though, we do *)
(* a primary lookup in case the fp in question has not *)
(* been evicted to external yet. *)
chkSnc: if (external # <<>>) {
(* Primary lookup. *)
cntns: while (index < L) {
if (isMatch(fp, idx(fp, index), table)) {
goto pick
} else {
if (isEmpty(idx(fp, index), table)) {
(* Found an EMPTY position which proves *)
(* that fp cannot be found at higher *)
(* positions. Thus, no need to continue.*)
expected := minimum(expected, index);
goto onSnc;
} else {
if (isMarked(idx(fp, index), table)) {
(* None of the lower positions has *)
(* fp, thus keep the lowest position *)
(* for the second loop as the start *)
(* index. No point in checking known *)
(* lower positions in the loop again. *)
expected := minimum(expected, index);
index := index + 1;
} else {
index := index + 1
}
}
}
};
(* External lookup. *)
onSnc: if (containsElem(external,fp)) {
goto pick
} else {
(* Have next loop start at expected determined *)
(* by previous loop. *)
index := expected;
(* Re-init expected to be used for its alternate purpose. *)
expected := -1;
};
};
(* Put inserts the given fp into the hash table by sequentially *)
(* trying the primary to the P's alternate position. *)
insrt: while (index < L) {
expected := table[idx(fp,index)];
if (expected = empty \/
(expected < 0 /\ expected # (-1) * fp)) {
cas: CAS(result, idx(fp,index), expected, fp);
if (result) {
history := history \cup {fp};
goto pick
} else {
(* Has been occupied in the meantime, *)
(* try to find another position. *)
goto insrt
}
};
(* Has fp been inserted by another process? Check isMatch *)
(* AFTER empty and on-external because of two reasons: *)
(* a) Thread A finds table[pos] to be empty but fails *)
(* to CAS fpX. Thread B concurrently also finds *)
(* table[pos] to be empty and succeeds to CAS fpX. *)
(* b) Iff table[pos] is empty or -1, higher positions *)
(* cannot be a match. *)
isMth: if (isMatch(fp,idx(fp,index),table)) {
goto pick
} else {
index := index + 1;
};
}; \* end of while/insrt
(* We failed to insert fp into a full table, thus try *)
(* to become the thread that evicts to external. *)
(* The label tryEv makes sure, that the read and *)
(* write occur atomically. An imlementation has to *)
(* CAS or use some other mechanism to control *)
(* concurrenry. *)
tryEv: if (evict = FALSE) {
(* CAS evict! *)
evict := TRUE;
(* Wait for all other insertion threads and *)
(* the one reader to park. *)
waitIns: await waitCnt = Cardinality(Writer) - 1;
\* waitIns: await waitCnt = Cardinality(Writer) - 1 + Cardinality(Reader);
call Evict();
endEv: evict := FALSE;
goto put;
} else {
goto put
}
}
} \* end while/pick
}
}
*** this ends the comment containg the pluscal code **********)
----------------------------------------------------------------------------
\* BEGIN TRANSLATION
VARIABLES table, external, newexternal, evict, waitCnt, history, pc, stack,
ei, ej, lo, fp, index, result, expected
vars == << table, external, newexternal, evict, waitCnt, history, pc, stack,
ei, ej, lo, fp, index, result, expected >>
ProcSet == (Writer)
Init == (* Global variables *)
/\ table = [i \in 1..K |-> empty]
/\ external = <<>>
/\ newexternal = <<>>
/\ evict = FALSE
/\ waitCnt = 0
/\ history = {}
(* Procedure Evict *)
/\ ei = [ self \in ProcSet |-> 1]
/\ ej = [ self \in ProcSet |-> 1]
/\ lo = [ self \in ProcSet |-> 0]
(* Process p *)
/\ fp = [self \in Writer |-> 0]
/\ index = [self \in Writer |-> 0]
/\ result = [self \in Writer |-> FALSE]
/\ expected = [self \in Writer |-> -1]
/\ stack = [self \in ProcSet |-> << >>]
/\ pc = [self \in ProcSet |-> "pick"]
strIns(self) == /\ pc[self] = "strIns"
/\ IF ei[self] <= K+L
THEN /\ lo' = [lo EXCEPT ![self] = table[mod(ei[self] + 1, K)]]
/\ pc' = [pc EXCEPT ![self] = "nestedIns"]
/\ ei' = ei
ELSE /\ ei' = [ei EXCEPT ![self] = 1]
/\ pc' = [pc EXCEPT ![self] = "flush"]
/\ lo' = lo
/\ UNCHANGED << table, external, newexternal, evict, waitCnt,
history, stack, ej, fp, index, result,
expected >>
nestedIns(self) == /\ pc[self] = "nestedIns"
/\ IF compare(lo[self], mod(ei[self] + 1, K),
table[mod(ej[self], K)], mod(ej[self], K)) <= -1
THEN /\ table' = [table EXCEPT ![mod(ej[self] + 1, K)] = table[mod(ej[self], K)]]
/\ IF ej[self] = 0
THEN /\ ej' = [ej EXCEPT ![self] = ej[self] - 1]
/\ pc' = [pc EXCEPT ![self] = "set"]
ELSE /\ ej' = [ej EXCEPT ![self] = ej[self] - 1]
/\ pc' = [pc EXCEPT ![self] = "nestedIns"]
ELSE /\ pc' = [pc EXCEPT ![self] = "set"]
/\ UNCHANGED << table, ej >>
/\ UNCHANGED << external, newexternal, evict, waitCnt,
history, stack, ei, lo, fp, index, result,
expected >>
set(self) == /\ pc[self] = "set"
/\ table' = [table EXCEPT ![mod(ej[self] + 1, K)] = lo[self]]
/\ ej' = [ej EXCEPT ![self] = ei[self] + 1]
/\ ei' = [ei EXCEPT ![self] = ei[self] + 1]
/\ pc' = [pc EXCEPT ![self] = "strIns"]
/\ UNCHANGED << external, newexternal, evict, waitCnt, history,
stack, lo, fp, index, result, expected >>
flush(self) == /\ pc[self] = "flush"
/\ IF ei[self] <= K+L
THEN /\ lo' = [lo EXCEPT ![self] = table[mod(ei[self], K)]]
/\ IF lo'[self] # empty /\
lo'[self] > largestElem(newexternal) /\
((ei[self] <= K /\ ~wrapped(lo'[self],ei[self])) \/
(ei[self] > K /\ wrapped(lo'[self],ei[self])))
THEN /\ newexternal' = Append(newexternal \o
subSeqSmaller(external, newexternal, lo'[self]), lo'[self])
/\ table' = [table EXCEPT ![mod(ei[self], K)] = lo'[self] * (-1)]
ELSE /\ TRUE
/\ UNCHANGED << table, newexternal >>
/\ ei' = [ei EXCEPT ![self] = ei[self] + 1]
/\ pc' = [pc EXCEPT ![self] = "flush"]
/\ UNCHANGED external
ELSE /\ external' = newexternal \o
subSeqLarger(external, newexternal)
/\ newexternal' = <<>>
/\ pc' = [pc EXCEPT ![self] = "rtrn"]
/\ UNCHANGED << table, ei, lo >>
/\ UNCHANGED << evict, waitCnt, history, stack, ej, fp, index,
result, expected >>
rtrn(self) == /\ pc[self] = "rtrn"
/\ pc' = [pc EXCEPT ![self] = Head(stack[self]).pc]
/\ ei' = [ei EXCEPT ![self] = Head(stack[self]).ei]
/\ ej' = [ej EXCEPT ![self] = Head(stack[self]).ej]
/\ lo' = [lo EXCEPT ![self] = Head(stack[self]).lo]
/\ stack' = [stack EXCEPT ![self] = Tail(stack[self])]
/\ UNCHANGED << table, external, newexternal, evict, waitCnt,
history, fp, index, result, expected >>
Evict(self) == strIns(self) \/ nestedIns(self) \/ set(self) \/ flush(self)
\/ rtrn(self)
pick(self) == /\ pc[self] = "pick"
/\ IF (fps \ history) = {}
THEN /\ pc' = [pc EXCEPT ![self] = "Done"]
/\ fp' = fp
ELSE /\ \E f \in (fps \ history):
fp' = [fp EXCEPT ![self] = f]
/\ pc' = [pc EXCEPT ![self] = "put"]
/\ UNCHANGED << table, external, newexternal, evict, waitCnt,
history, stack, ei, ej, lo, index, result,
expected >>
put(self) == /\ pc[self] = "put"
/\ index' = [index EXCEPT ![self] = 0]
/\ result' = [result EXCEPT ![self] = FALSE]
/\ expected' = [expected EXCEPT ![self] = L]
/\ IF evict
THEN /\ waitCnt' = waitCnt + 1
/\ pc' = [pc EXCEPT ![self] = "waitEv"]
ELSE /\ pc' = [pc EXCEPT ![self] = "chkSnc"]
/\ UNCHANGED waitCnt
/\ UNCHANGED << table, external, newexternal, evict, history,
stack, ei, ej, lo, fp >>
waitEv(self) == /\ pc[self] = "waitEv"
/\ evict = FALSE
/\ pc' = [pc EXCEPT ![self] = "endWEv"]
/\ UNCHANGED << table, external, newexternal, evict, waitCnt,
history, stack, ei, ej, lo, fp, index, result,
expected >>
endWEv(self) == /\ pc[self] = "endWEv"
/\ waitCnt' = waitCnt - 1
/\ pc' = [pc EXCEPT ![self] = "put"]
/\ UNCHANGED << table, external, newexternal, evict, history,
stack, ei, ej, lo, fp, index, result, expected >>
chkSnc(self) == /\ pc[self] = "chkSnc"
/\ IF external # <<>>
THEN /\ pc' = [pc EXCEPT ![self] = "cntns"]
ELSE /\ pc' = [pc EXCEPT ![self] = "insrt"]
/\ UNCHANGED << table, external, newexternal, evict, waitCnt,
history, stack, ei, ej, lo, fp, index, result,
expected >>
cntns(self) == /\ pc[self] = "cntns"
/\ IF index[self] < L
THEN /\ IF isMatch(fp[self], idx(fp[self], index[self]), table)
THEN /\ pc' = [pc EXCEPT ![self] = "pick"]
/\ UNCHANGED << index, expected >>
ELSE /\ IF isEmpty(idx(fp[self], index[self]), table)
THEN /\ expected' = [expected EXCEPT ![self] = minimum(expected[self], index[self])]
/\ pc' = [pc EXCEPT ![self] = "onSnc"]
/\ index' = index
ELSE /\ IF isMarked(idx(fp[self], index[self]), table)
THEN /\ expected' = [expected EXCEPT ![self] = minimum(expected[self], index[self])]
/\ index' = [index EXCEPT ![self] = index[self] + 1]
ELSE /\ index' = [index EXCEPT ![self] = index[self] + 1]
/\ UNCHANGED expected
/\ pc' = [pc EXCEPT ![self] = "cntns"]
ELSE /\ pc' = [pc EXCEPT ![self] = "onSnc"]
/\ UNCHANGED << index, expected >>
/\ UNCHANGED << table, external, newexternal, evict, waitCnt,
history, stack, ei, ej, lo, fp, result >>
onSnc(self) == /\ pc[self] = "onSnc"
/\ IF containsElem(external,fp[self])
THEN /\ pc' = [pc EXCEPT ![self] = "pick"]
/\ UNCHANGED << index, expected >>
ELSE /\ index' = [index EXCEPT ![self] = expected[self]]
/\ expected' = [expected EXCEPT ![self] = -1]
/\ pc' = [pc EXCEPT ![self] = "insrt"]
/\ UNCHANGED << table, external, newexternal, evict, waitCnt,
history, stack, ei, ej, lo, fp, result >>
insrt(self) == /\ pc[self] = "insrt"
/\ IF index[self] < L
THEN /\ expected' = [expected EXCEPT ![self] = table[idx(fp[self],index[self])]]
/\ IF expected'[self] = empty \/
(expected'[self] < 0 /\ expected'[self] # (-1) * fp[self])
THEN /\ pc' = [pc EXCEPT ![self] = "cas"]
ELSE /\ pc' = [pc EXCEPT ![self] = "isMth"]
ELSE /\ pc' = [pc EXCEPT ![self] = "tryEv"]
/\ UNCHANGED expected
/\ UNCHANGED << table, external, newexternal, evict, waitCnt,
history, stack, ei, ej, lo, fp, index, result >>
isMth(self) == /\ pc[self] = "isMth"
/\ IF isMatch(fp[self],idx(fp[self],index[self]),table)
THEN /\ pc' = [pc EXCEPT ![self] = "pick"]
/\ index' = index
ELSE /\ index' = [index EXCEPT ![self] = index[self] + 1]
/\ pc' = [pc EXCEPT ![self] = "insrt"]
/\ UNCHANGED << table, external, newexternal, evict, waitCnt,
history, stack, ei, ej, lo, fp, result,
expected >>
cas(self) == /\ pc[self] = "cas"
/\ IF table[(idx(fp[self],index[self]))] = expected[self]
THEN /\ table' = [table EXCEPT ![(idx(fp[self],index[self]))] = fp[self]]
/\ result' = [result EXCEPT ![self] = TRUE]
ELSE /\ result' = [result EXCEPT ![self] = FALSE]
/\ table' = table
/\ IF result'[self]
THEN /\ history' = (history \cup {fp[self]})
/\ pc' = [pc EXCEPT ![self] = "pick"]
ELSE /\ pc' = [pc EXCEPT ![self] = "insrt"]
/\ UNCHANGED history
/\ UNCHANGED << external, newexternal, evict, waitCnt, stack, ei,
ej, lo, fp, index, expected >>
tryEv(self) == /\ pc[self] = "tryEv"
/\ IF evict = FALSE
THEN /\ evict' = TRUE
/\ pc' = [pc EXCEPT ![self] = "waitIns"]
ELSE /\ pc' = [pc EXCEPT ![self] = "put"]
/\ evict' = evict
/\ UNCHANGED << table, external, newexternal, waitCnt, history,
stack, ei, ej, lo, fp, index, result, expected >>
waitIns(self) == /\ pc[self] = "waitIns"
/\ waitCnt = Cardinality(Writer) - 1
/\ stack' = [stack EXCEPT ![self] = << [ procedure |-> "Evict",
pc |-> "endEv",
ei |-> ei[self],
ej |-> ej[self],
lo |-> lo[self] ] >>
\o stack[self]]
/\ ei' = [ei EXCEPT ![self] = 1]
/\ ej' = [ej EXCEPT ![self] = 1]
/\ lo' = [lo EXCEPT ![self] = 0]
/\ pc' = [pc EXCEPT ![self] = "strIns"]
/\ UNCHANGED << table, external, newexternal, evict, waitCnt,
history, fp, index, result, expected >>
endEv(self) == /\ pc[self] = "endEv"
/\ evict' = FALSE
/\ pc' = [pc EXCEPT ![self] = "put"]
/\ UNCHANGED << table, external, newexternal, waitCnt, history,
stack, ei, ej, lo, fp, index, result, expected >>
p(self) == pick(self) \/ put(self) \/ waitEv(self) \/ endWEv(self)
\/ chkSnc(self) \/ cntns(self) \/ onSnc(self) \/ insrt(self)
\/ isMth(self) \/ cas(self) \/ tryEv(self) \/ waitIns(self)
\/ endEv(self)
Next == (\E self \in ProcSet: Evict(self))
\/ (\E self \in Writer: p(self))
\/ (* Disjunct to prevent deadlock on termination *)
((\A self \in ProcSet: pc[self] = "Done") /\ UNCHANGED vars)
Spec == /\ Init /\ [][Next]_vars
/\ \A self \in Writer : WF_vars(p(self)) /\ WF_vars(Evict(self))
Termination == <>(\A self \in ProcSet: pc[self] = "Done")
\* END TRANSLATION
----------------------------------------------------------------------------
contains(f,t,seq,Q) == \/ \E i \in 0..Q: isMatch(f,idx(f,i),t)
\/ \E i \in 1..Len(seq): seq[i] = f
\/ IF f \in (Image(lo) \ {0}) THEN evict = TRUE
ELSE FALSE
(***************************************************************************)
(* All fingerprint in history are (always) members of the seen set C, *)
(* all (fps \ history) never are. *)
(* During eviction, the sort algorithm might swap two fingerprints *)
(* non-atomically s.t. the table does not contain one of the two *)
(* fingerprints. The one not it table is then expected to be in the lo *)
(* variable of the sort algorithms. *)
(***************************************************************************)
Contains == /\ \A seen \in history:
contains(seen,table,external,L)
/\ \A unseen \in (fps \ history):
~contains(unseen,table,external,L)
----------------------------------------------------------------------------
(***************************************************************************)
(* The absolute value of the given number. *)
(***************************************************************************)
abs(number) == IF number < 0 THEN -1 * number ELSE number
(***************************************************************************)
(* True when no eviction is running. *)
(***************************************************************************)
FindOrPut == evict = FALSE
(***************************************************************************)
(* FALSE iff table contains duplicate elements (excluding empty), unless *)
(* Evict is running. *)
(* During eviction, the sort algorithm might swap two fingerprints *)
(* non-atomically s.t. the table contains duplicates of one of the two *)
(* fingerprints temporarily. *)
(***************************************************************************)
noDupes(range, t, emp) == LET sub == SelectSeq(t, LAMBDA e: e # emp)
IN IF Len(sub) < 2 THEN TRUE
ELSE /\ IF \E i \in 1..30: 2^i = TLCGet(42)[2] THEN PrintT(TLCGet(42)) ELSE TRUE \* Print counter
/\ TLCSet(42, [TLCGet(42) EXCEPT ![2] = @ + 1]) \* Increment counter
/\ \A i \in 1..(Len(sub) - 1):
\A j \in (i+1)..Len(sub):
/\ abs(sub[i]) # abs(sub[j])
Duplicates == FindOrPut => noDupes(fps, table, empty)
noDupes2(range, t, emp) == LET sub == SelectSeq(t, LAMBDA e: e # emp)
IN IF Len(sub) < 2 THEN TRUE
ELSE \A i \in 1..(Len(sub) - 1):
\A j \in (i+1)..Len(sub):
/\ abs(sub[i]) # abs(sub[j])
DuplicatsNoModuleOverride == FindOrPut => noDupes2(fps, table, empty)
----------------------------------------------------------------------------
(***************************************************************************)
(* seq is sorted iff its empty-filtered sub-sequence is sorted. An empty *)
(* sequence is defined to be sorted. *)
(***************************************************************************)
isSorted(seq) == LET sub == SelectSeq(seq, LAMBDA e: e # empty)
IN IF Len(sub) < 2 THEN TRUE
ELSE \A i \in 1..(Len(sub) - 1):
sub[i] < sub[i+1]
(***************************************************************************)
(* External storage is always sorted in ascending order. *)
(***************************************************************************)
Sorted == isSorted(external) /\ isSorted(newexternal)
----------------------------------------------------------------------------
(***************************************************************************)
(* TRUE iff f is found in table within idx(f,0)..id(f,L). *)
(***************************************************************************)
containedInTable(f) == \E l \in 0..L: table[idx(abs(f), l)] = f
(***************************************************************************)
(* TRUE if all fingerprints \in history correctly transition from table *)
(* to the external storage. Models a three state FSM. *)
(***************************************************************************)
Consistent == FindOrPut => \A seen \in history:
/\ containedInTable(seen) => ~containsElem(external, seen)
/\ containedInTable(seen * (-1)) => containsElem(external, seen)
/\ ~containedInTable(seen) => containsElem(external, seen)
----------------------------------------------------------------------------
(***************************************************************************)
(* Under all behaviors, the algorithm makes progress and eventually puts *)
(* all fingerprints in fps into the table resulting in history = fps. *)
(***************************************************************************)
Complete == <>[](history = fps)
(***************************************************************************)
(* Iff certain that Termination is guaranteed, the liveness property *)
(* Complete can be rewritten to the safety property below. A safety *)
(* property can be checked faster. *)
(***************************************************************************)
CompleteAsSafety == \A self \in ProcSet: pc[self] = "Done" => (history = fps)
=============================================================================
\end{ppcal}
2 cores - 08% mem
0:44 - 304553
2 cores - 88% mem
1:20 - 304553 (symmetry on w and r)
4 cores - 88% mem
1:47 - 605857 (no symmertry)
1:10 - 304553 (symmetry on w and r)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment