diff --git a/tla/.gitignore b/tla/.gitignore new file mode 100644 index 0000000..9469ad2 --- /dev/null +++ b/tla/.gitignore @@ -0,0 +1,5 @@ +*.old +**/*.toolbox/** +!**/*.toolbox/*Model.launch + +states/ diff --git a/tla/cas_exchange_loop/original/cas_exchange_loop.tla b/tla/cas_exchange_loop/original/cas_exchange_loop.tla new file mode 100644 index 0000000..f459913 --- /dev/null +++ b/tla/cas_exchange_loop/original/cas_exchange_loop.tla @@ -0,0 +1,432 @@ +------------------------- MODULE cas_exchange_loop ------------------------- + +(*************************************************************************** + +This algorithm was originally introduced by Dave Rowland and Fabien Renn-Giles +in Real-time 101 - Part II: The real-time audio developer's toolbox +(https://youtu.be/PoZAo2Vikbo?t=297). + +The original idea for this algorithm is for the writer to be on the slow +(non-real-time) path and reader to be on the fast (real-time) path. + +The idea is to use an atomic pointer and a heap-allocated object that's always +tracked. The atomic pointer will point to nullptr if the fast-path reader is +currently reading the value. Otherwise, it will point to the heap-allocated +object (or an older version of the heap-allocated object). Effectively, this is +using the nullptr as an "in-use" flag for this atomic pointer. The tracked +heap-allocated object stores the "latest" object. + +The slow-path reader allocates a new object and CAS the atomic pointer if the +atomic pointer is currently not null. If it is successfuly, it has exchanged +the pointer to the new object with the pointer to the previous iteration of the +object. The slow-path then updates the tracked heap-allocated object to be +the new object, while deleting the previous object to avoid a memory leak. + +This algorithm is able to send an object of any size to the fast thread without +copying. It requires memory allocation and deallocation on the slow-path. The +fast-path is wait-free while the slow-path is lock-free (if memory allocation +is lock-free). This supports at most one reader and one writer. + +The goal of this specification is to write the above down and check for the +following properties: + +1. No memory leak occurs (InvariantNoMemoryLeak) +2. No torn read occurs on the fast path (assertions in reader process) +3. No use after free (assertions in readers and writers) +4. No data race occurs (TODO) +5. The data is passed from the writer to the reader successfully at the end. + + ***************************************************************************) + +EXTENDS Sequences, Integers, TLC, FiniteSets + +(*************************************************************************** +This defines the maximum limit of the memory in the model. + +For modeling purposes, this should be above 2 as the algorithm should have +at most two objects allocated at a time. + ***************************************************************************) +CONSTANT kMemoryCapacity + +(*************************************************************************** +An Object is something we store in the memory. Each object consists of multiple +ObjectElements, as we need to model torn writes/reads. + +For modeling purposes, these should be model values like {e0, e1}. Two +elements is likely enough. + ***************************************************************************) +CONSTANT ObjectElements + +(*************************************************************************** +This is the size of the object. Each object has kObjectSize ObjectElements. + +For modeling, it must be greater than 1. + ***************************************************************************) +CONSTANT kObjectSize + +(*************************************************************************** +NULLPTR: A null pointer +EMPTY: A marker showing the memory slot is empty + ***************************************************************************) +CONSTANTS NULLPTR, EMPTY + +(*************************************************************************** +An uninitialized object element. + ***************************************************************************) +UninitializedElement == CHOOSE e: e \notin ObjectElements + +(*************************************************************************** +Ways to create objects + ***************************************************************************) +ObjectWithValue(value) == [i \in 1..kObjectSize |-> value] +DefaultObject == ObjectWithValue(UninitializedElement) + +\* Pick a random object element for the initial state. Could theoretically make +\* the initial be different objects. +InitialObject == ObjectWithValue(CHOOSE e \in ObjectElements: TRUE) + +(* --algorithm cas_exchange_loop + +variables + g_storage_pointer \in 1..kMemoryCapacity, \* storage in the original + g_memory = [i \in 1..kMemoryCapacity |-> IF i = g_storage_pointer THEN InitialObject ELSE EMPTY], \* Need to model the memory allocation + g_atomic_pointer = g_storage_pointer, \* biquadCoeff in the original + g_writer_written_data_for_assertion = EMPTY; \* We atomically use TLA+ to write to this variable, so we can do an equality assertion to make sure we read the right data. + +define + MemoryUsage == Cardinality({i \in 1..kMemoryCapacity: g_memory[i] # EMPTY}) + InvariantNoMemoryLeak == MemoryUsage <= 2 + + DataIsNotTorn(data) == Cardinality({data[i] : i \in 1..kObjectSize}) = 1 +end define; + +fair process reader = "reader" +variables + reader_local_pointer, \* coeff in the original + read_index = 1, \* Used to model reading through the data in multiple steps. + reader_local_data = DefaultObject; +begin + ReaderExchangePtr: + assert g_atomic_pointer # NULLPTR; + reader_local_pointer := g_atomic_pointer; + g_atomic_pointer := NULLPTR; + + ReaderCopyObject: + while read_index <= kObjectSize do + assert g_memory[reader_local_pointer] # EMPTY; + assert DataIsNotTorn(g_memory[reader_local_pointer]); + reader_local_data[read_index] := g_memory[reader_local_pointer][read_index]; + ReaderReadObjectInc: + read_index := read_index + 1; + end while; + + ReaderStorePtr: + assert DataIsNotTorn(reader_local_data); + assert \/ g_writer_written_data_for_assertion = EMPTY + \/ g_writer_written_data_for_assertion = reader_local_data; + g_atomic_pointer := reader_local_pointer; +end process; + +fair process writer = "writer" +variables + writer_local_pointer, \* newBiquad in original + chosen_element, \* A shortcut variable to choose a single element from ObjectElements so it is stationary in the write loop. May be replaced with `with`? + writer_expected_ptr, \* expected in the origin + exchanged = FALSE, \* A variable to indicate if the exchange is successful. + write_index = 1; \* Used to model writing through the data in multiple steps. +begin + \* We first start by finding a free slot in memory and allocate the memory + \* with a default initialized object. + \* + \* If this CHOOSE ever fails, it means we have run out of memory and likely + \* there was a memory leak. + WriterAllocateObject: + assert Cardinality({i \in DOMAIN g_memory : g_memory[i] = EMPTY}) > 0; \* Check OOM + + writer_local_pointer := CHOOSE i \in DOMAIN g_memory: g_memory[i] = EMPTY; + + \* Assign a default object. + g_memory[writer_local_pointer] := DefaultObject; + + + \* In this specification, we always write the same object element value to + \* the object. This makes it easy to check if a torn read/write occured + \* as the invariant for that simply becomes whether or not the number of + \* unique object values is 1 or not in an object. + \* + \* Maybe this is not the best way to choose an element as it introduces yet + \* another step. However, I'm not sure how to fix it. + \* TODO: fix this. + WriterChooseObjectElement: + with elem \in ObjectElements do + chosen_element := elem; + end with; + + \* In this specification, we need to model the data write, to make sure + \* that writes here doesn't actually affect the reads. This is similar to + \* the copy constructor that's called when the original code's + \* changeBiquadParameters is called. + WriterWriteObject: + while write_index <= kObjectSize do + assert g_memory[writer_local_pointer] # EMPTY; \* Check use after free + g_memory[writer_local_pointer][write_index] := chosen_element; + WriterWriteObjectLoopInc: + write_index := write_index + 1; + end while; + + WriterCASLoop: + while exchanged = FALSE do + writer_expected_ptr := g_storage_pointer; + WriterCASCAS: + if (writer_expected_ptr = g_atomic_pointer) then + g_atomic_pointer := writer_local_pointer || writer_local_pointer := g_atomic_pointer; + g_writer_written_data_for_assertion := ObjectWithValue(chosen_element); \* Not a part of the algorithm. Not implementable in real-life. Used for assertion instead. + exchanged := TRUE; + end if; + end while; + + WriterUpdateStoragePointer: + g_storage_pointer := writer_local_pointer; + + WriterDeleteOldData: + assert g_memory[writer_local_pointer] # EMPTY; \* Check double free + g_memory[writer_local_pointer] := EMPTY; +end process; + +end algorithm; *) +\* BEGIN TRANSLATION (chksum(pcal) = "68798429" /\ chksum(tla) = "4d4ee443") +CONSTANT defaultInitValue +VARIABLES g_storage_pointer, g_memory, g_atomic_pointer, + g_writer_written_data_for_assertion, pc + +(* define statement *) +MemoryUsage == Cardinality({i \in 1..kMemoryCapacity: g_memory[i] # EMPTY}) +InvariantNoMemoryLeak == MemoryUsage <= 2 + +DataIsNotTorn(data) == Cardinality({data[i] : i \in 1..kObjectSize}) = 1 + +VARIABLES reader_local_pointer, read_index, reader_local_data, + writer_local_pointer, chosen_element, writer_expected_ptr, + exchanged, write_index + +vars == << g_storage_pointer, g_memory, g_atomic_pointer, + g_writer_written_data_for_assertion, pc, reader_local_pointer, + read_index, reader_local_data, writer_local_pointer, + chosen_element, writer_expected_ptr, exchanged, write_index >> + +ProcSet == {"reader"} \cup {"writer"} + +Init == (* Global variables *) + /\ g_storage_pointer \in 1..kMemoryCapacity + /\ g_memory = [i \in 1..kMemoryCapacity |-> IF i = g_storage_pointer THEN InitialObject ELSE EMPTY] + /\ g_atomic_pointer = g_storage_pointer + /\ g_writer_written_data_for_assertion = EMPTY + (* Process reader *) + /\ reader_local_pointer = defaultInitValue + /\ read_index = 1 + /\ reader_local_data = DefaultObject + (* Process writer *) + /\ writer_local_pointer = defaultInitValue + /\ chosen_element = defaultInitValue + /\ writer_expected_ptr = defaultInitValue + /\ exchanged = FALSE + /\ write_index = 1 + /\ pc = [self \in ProcSet |-> CASE self = "reader" -> "ReaderExchangePtr" + [] self = "writer" -> "WriterAllocateObject"] + +ReaderExchangePtr == /\ pc["reader"] = "ReaderExchangePtr" + /\ Assert(g_atomic_pointer # NULLPTR, + "Failure of assertion at line 110, column 9.") + /\ reader_local_pointer' = g_atomic_pointer + /\ g_atomic_pointer' = NULLPTR + /\ pc' = [pc EXCEPT !["reader"] = "ReaderCopyObject"] + /\ UNCHANGED << g_storage_pointer, g_memory, + g_writer_written_data_for_assertion, + read_index, reader_local_data, + writer_local_pointer, chosen_element, + writer_expected_ptr, exchanged, + write_index >> + +ReaderCopyObject == /\ pc["reader"] = "ReaderCopyObject" + /\ IF read_index <= kObjectSize + THEN /\ Assert(g_memory[reader_local_pointer] # EMPTY, + "Failure of assertion at line 116, column 13.") + /\ Assert(DataIsNotTorn(g_memory[reader_local_pointer]), + "Failure of assertion at line 117, column 13.") + /\ reader_local_data' = [reader_local_data EXCEPT ![read_index] = g_memory[reader_local_pointer][read_index]] + /\ pc' = [pc EXCEPT !["reader"] = "ReaderReadObjectInc"] + ELSE /\ pc' = [pc EXCEPT !["reader"] = "ReaderStorePtr"] + /\ UNCHANGED reader_local_data + /\ UNCHANGED << g_storage_pointer, g_memory, + g_atomic_pointer, + g_writer_written_data_for_assertion, + reader_local_pointer, read_index, + writer_local_pointer, chosen_element, + writer_expected_ptr, exchanged, + write_index >> + +ReaderReadObjectInc == /\ pc["reader"] = "ReaderReadObjectInc" + /\ read_index' = read_index + 1 + /\ pc' = [pc EXCEPT !["reader"] = "ReaderCopyObject"] + /\ UNCHANGED << g_storage_pointer, g_memory, + g_atomic_pointer, + g_writer_written_data_for_assertion, + reader_local_pointer, reader_local_data, + writer_local_pointer, chosen_element, + writer_expected_ptr, exchanged, + write_index >> + +ReaderStorePtr == /\ pc["reader"] = "ReaderStorePtr" + /\ Assert(DataIsNotTorn(reader_local_data), + "Failure of assertion at line 124, column 9.") + /\ Assert(\/ g_writer_written_data_for_assertion = EMPTY + \/ g_writer_written_data_for_assertion = reader_local_data, + "Failure of assertion at line 125, column 9.") + /\ g_atomic_pointer' = reader_local_pointer + /\ pc' = [pc EXCEPT !["reader"] = "Done"] + /\ UNCHANGED << g_storage_pointer, g_memory, + g_writer_written_data_for_assertion, + reader_local_pointer, read_index, + reader_local_data, writer_local_pointer, + chosen_element, writer_expected_ptr, + exchanged, write_index >> + +reader == ReaderExchangePtr \/ ReaderCopyObject \/ ReaderReadObjectInc + \/ ReaderStorePtr + +WriterAllocateObject == /\ pc["writer"] = "WriterAllocateObject" + /\ Assert(Cardinality({i \in DOMAIN g_memory : g_memory[i] = EMPTY}) > 0, + "Failure of assertion at line 144, column 9.") + /\ writer_local_pointer' = (CHOOSE i \in DOMAIN g_memory: g_memory[i] = EMPTY) + /\ g_memory' = [g_memory EXCEPT ![writer_local_pointer'] = DefaultObject] + /\ pc' = [pc EXCEPT !["writer"] = "WriterChooseObjectElement"] + /\ UNCHANGED << g_storage_pointer, g_atomic_pointer, + g_writer_written_data_for_assertion, + reader_local_pointer, read_index, + reader_local_data, chosen_element, + writer_expected_ptr, exchanged, + write_index >> + +WriterChooseObjectElement == /\ pc["writer"] = "WriterChooseObjectElement" + /\ \E elem \in ObjectElements: + chosen_element' = elem + /\ pc' = [pc EXCEPT !["writer"] = "WriterWriteObject"] + /\ UNCHANGED << g_storage_pointer, g_memory, + g_atomic_pointer, + g_writer_written_data_for_assertion, + reader_local_pointer, read_index, + reader_local_data, + writer_local_pointer, + writer_expected_ptr, exchanged, + write_index >> + +WriterWriteObject == /\ pc["writer"] = "WriterWriteObject" + /\ IF write_index <= kObjectSize + THEN /\ Assert(g_memory[writer_local_pointer] # EMPTY, + "Failure of assertion at line 171, column 13.") + /\ g_memory' = [g_memory EXCEPT ![writer_local_pointer][write_index] = chosen_element] + /\ pc' = [pc EXCEPT !["writer"] = "WriterWriteObjectLoopInc"] + ELSE /\ pc' = [pc EXCEPT !["writer"] = "WriterCASLoop"] + /\ UNCHANGED g_memory + /\ UNCHANGED << g_storage_pointer, g_atomic_pointer, + g_writer_written_data_for_assertion, + reader_local_pointer, read_index, + reader_local_data, writer_local_pointer, + chosen_element, writer_expected_ptr, + exchanged, write_index >> + +WriterWriteObjectLoopInc == /\ pc["writer"] = "WriterWriteObjectLoopInc" + /\ write_index' = write_index + 1 + /\ pc' = [pc EXCEPT !["writer"] = "WriterWriteObject"] + /\ UNCHANGED << g_storage_pointer, g_memory, + g_atomic_pointer, + g_writer_written_data_for_assertion, + reader_local_pointer, read_index, + reader_local_data, + writer_local_pointer, + chosen_element, + writer_expected_ptr, exchanged >> + +WriterCASLoop == /\ pc["writer"] = "WriterCASLoop" + /\ IF exchanged = FALSE + THEN /\ writer_expected_ptr' = g_storage_pointer + /\ pc' = [pc EXCEPT !["writer"] = "WriterCASCAS"] + ELSE /\ pc' = [pc EXCEPT !["writer"] = "WriterUpdateStoragePointer"] + /\ UNCHANGED writer_expected_ptr + /\ UNCHANGED << g_storage_pointer, g_memory, g_atomic_pointer, + g_writer_written_data_for_assertion, + reader_local_pointer, read_index, + reader_local_data, writer_local_pointer, + chosen_element, exchanged, write_index >> + +WriterCASCAS == /\ pc["writer"] = "WriterCASCAS" + /\ IF (writer_expected_ptr = g_atomic_pointer) + THEN /\ /\ g_atomic_pointer' = writer_local_pointer + /\ writer_local_pointer' = g_atomic_pointer + /\ g_writer_written_data_for_assertion' = ObjectWithValue(chosen_element) + /\ exchanged' = TRUE + ELSE /\ TRUE + /\ UNCHANGED << g_atomic_pointer, + g_writer_written_data_for_assertion, + writer_local_pointer, exchanged >> + /\ pc' = [pc EXCEPT !["writer"] = "WriterCASLoop"] + /\ UNCHANGED << g_storage_pointer, g_memory, + reader_local_pointer, read_index, + reader_local_data, chosen_element, + writer_expected_ptr, write_index >> + +WriterUpdateStoragePointer == /\ pc["writer"] = "WriterUpdateStoragePointer" + /\ g_storage_pointer' = writer_local_pointer + /\ pc' = [pc EXCEPT !["writer"] = "WriterDeleteOldData"] + /\ UNCHANGED << g_memory, g_atomic_pointer, + g_writer_written_data_for_assertion, + reader_local_pointer, read_index, + reader_local_data, + writer_local_pointer, + chosen_element, + writer_expected_ptr, exchanged, + write_index >> + +WriterDeleteOldData == /\ pc["writer"] = "WriterDeleteOldData" + /\ Assert(g_memory[writer_local_pointer] # EMPTY, + "Failure of assertion at line 192, column 9.") + /\ g_memory' = [g_memory EXCEPT ![writer_local_pointer] = EMPTY] + /\ pc' = [pc EXCEPT !["writer"] = "Done"] + /\ UNCHANGED << g_storage_pointer, g_atomic_pointer, + g_writer_written_data_for_assertion, + reader_local_pointer, read_index, + reader_local_data, writer_local_pointer, + chosen_element, writer_expected_ptr, + exchanged, write_index >> + +writer == WriterAllocateObject \/ WriterChooseObjectElement + \/ WriterWriteObject \/ WriterWriteObjectLoopInc + \/ WriterCASLoop \/ WriterCASCAS \/ WriterUpdateStoragePointer + \/ WriterDeleteOldData + +(* Allow infinite stuttering to prevent deadlock on termination. *) +Terminating == /\ \A self \in ProcSet: pc[self] = "Done" + /\ UNCHANGED vars + +Next == reader \/ writer + \/ Terminating + +Spec == /\ Init /\ [][Next]_vars + /\ WF_vars(reader) + /\ WF_vars(writer) + +Termination == <>(\A self \in ProcSet: pc[self] = "Done") + +\* END TRANSLATION + +WriterWroteDataBeforeReaderAtLeastOnce == + <>( + /\ (reader_local_data # DefaultObject) + /\ <>(g_writer_written_data_for_assertion # EMPTY) + /\ <>(g_writer_written_data_for_assertion = reader_local_data) + ) + +============================================================================= +\* Modification History +\* Last modified Sat Mar 23 17:13:26 EDT 2024 by shuhao +\* Created Sat Mar 23 11:26:21 EDT 2024 by shuhao diff --git a/tla/cas_exchange_loop/original/cas_exchange_loop.toolbox/cas_exchange_loop___SimpleModel.launch b/tla/cas_exchange_loop/original/cas_exchange_loop.toolbox/cas_exchange_loop___SimpleModel.launch new file mode 100644 index 0000000..0e92a3d --- /dev/null +++ b/tla/cas_exchange_loop/original/cas_exchange_loop.toolbox/cas_exchange_loop___SimpleModel.launch @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tla/cas_exchange_loop/wrong/cas_exchange_loop_wrong.tla b/tla/cas_exchange_loop/wrong/cas_exchange_loop_wrong.tla new file mode 100644 index 0000000..5e3f7e7 --- /dev/null +++ b/tla/cas_exchange_loop/wrong/cas_exchange_loop_wrong.tla @@ -0,0 +1,339 @@ +------------------------- MODULE cas_exchange_loop_wrong-------------------- + +(*************************************************************************** + +WARNING: THIS VERSION IS FOR THE WRONG IMPLEMENTATION FROM THE PRESENTATION. +IT IS HERE TO CHECK THAT THE TLA+ INVARIANTS AND TLC MODEL IS WORKING CORRECTLY. +PLEASE REFER TO cas_exchange_loop.tla FOR THE ACTUAL ALGORITHM SPECIFICATION. + +This algorithm was originally introduced by Dave Rowland and Fabien Renn-Giles +in Real-time 101 - Part II: The real-time audio developer's toolbox +(https://youtu.be/PoZAo2Vikbo?t=297). + +The original idea for this algorithm is for the writer to be on the slow +(non-real-time) path and reader to be on the fast (real-time) path. + +The idea is to use an atomic pointer and a heap-allocated object that's always +tracked. The atomic pointer will point to nullptr if the fast-path reader is +currently reading the value. Otherwise, it will point to the heap-allocated +object (or an older version of the heap-allocated object). Effectively, this is +using the nullptr as an "in-use" flag for this atomic pointer. The tracked +heap-allocated object stores the "latest" object. + +The slow-path reader allocates a new object and CAS the atomic pointer if the +atomic pointer is currently not null. If it is successfuly, it has exchanged +the pointer to the new object with the pointer to the previous iteration of the +object. The slow-path then updates the tracked heap-allocated object to be +the new object, while deleting the previous object to avoid a memory leak. + +This algorithm is able to send an object of any size to the fast thread without +copying. It requires memory allocation and deallocation on the slow-path. The +fast-path is wait-free while the slow-path is lock-free (if memory allocation +is lock-free). This supports at most one reader and one writer. + +The goal of this specification is to write the above down and check for the +following properties: + +1. No memory leak occurs. +2. No torn read occurs on the fast path. +3. No use after free +4. No data race occurs. + +NOTE: IN THIS BROKEN MODEL, WE CAN'T SEE THE MEMORY LEAK, TORN READ, OR DATA RACE. +ASSERTS WILL CATCH THE USE AFTER FREE PROBLEM DUE TO THE ABA PROBLEM. + + ***************************************************************************) + +EXTENDS Sequences, Integers, TLC + +(*************************************************************************** +This defines the maximum limit of the memory in the model. + +For modeling purposes, this should be above 2 as the algorithm should have +at most two objects allocated at a time. + ***************************************************************************) +CONSTANT kMemoryCapacity + +(*************************************************************************** +An Object is something we store in the memory. Each object consists of multiple +ObjectElements, as we need to model torn writes/reads. + +For modeling purposes, these should be model values like {e0, e1}. Two +elements is likely enough. + ***************************************************************************) +CONSTANT ObjectElements + +(*************************************************************************** +This is the size of the object. Each object has kObjectSize ObjectElements. + +For modeling, it must be greater than 1. + ***************************************************************************) +CONSTANT kObjectSize + +(*************************************************************************** +NULLPTR: A null pointer +EMPTY: A marker showing the memory slot is empty + ***************************************************************************) +CONSTANTS NULLPTR, EMPTY + +(*************************************************************************** +An uninitialized object element. + ***************************************************************************) +UninitializedElement == CHOOSE e: e \notin ObjectElements + +(*************************************************************************** +This is a default constructed object to initialize the storage as per the +original algorithm. We simply choose an arbitrary object of kObjectSize. +It doesn't matter what the make up of the object is as it shouldn't be read. + ***************************************************************************) +DefaultObject == [i \in 1..kObjectSize |-> UninitializedElement] + +(* --algorithm cas_exchange_loop + +variables + g_memory = [i \in 1..kMemoryCapacity |-> IF i = 1 THEN DefaultObject ELSE EMPTY], \* Need to model the memory allocation + g_storage_pointer = 1, \* storage in the original + g_in_use = FALSE; \* isInAudioThread in the original + +process reader = "reader" +variables + reader_local_pointer, \* coeff in the original + read_index = 1, \* Used to model reading through the data in multiple steps. + local_data = DefaultObject; +begin + ReaderSetInUseTrue: + g_in_use := TRUE; + + ReaderReadPointer: + reader_local_pointer := g_storage_pointer; + + ReaderCopyObject: + while read_index <= kObjectSize do + assert g_memory[reader_local_pointer] # EMPTY; + local_data[read_index] := g_memory[reader_local_pointer][read_index]; + ReaderReadObjectInc: + read_index := read_index + 1; + end while; + + ReaderSetInUseFalse: + g_in_use := FALSE; +end process; + +process writer = "writer" +variables + writer_local_pointer, \* ptr in original + chosen_element, \* A shortcut variable to choose a single element from ObjectElements so it is stationary in the write loop. May be replaced with `with`? + write_index = 1; \* Used to model writing through the data in multiple steps. +begin + \* We first start by finding a free slot in memory and allocate the memory + \* with a default initialized object. + \* + \* If this CHOOSE ever fails, it means we have run out of memory and likely + \* there was a memory leak. + WriterAllocateObject: + writer_local_pointer := CHOOSE i \in DOMAIN g_memory: g_memory[i] = EMPTY; + + \* Assign a default object. + g_memory[writer_local_pointer] := DefaultObject; + + + \* In this specification, we always write the same object element value to + \* the object. This makes it easy to check if a torn read/write occured + \* as the invariant for that simply becomes whether or not the number of + \* unique object values is 1 or not in an object. + \* + \* Maybe this is not the best way to choose an element as it introduces yet + \* another step. However, I'm not sure how to fix it. + \* TODO: fix this. + WriterChooseObjectElement: + with elem \in ObjectElements do + chosen_element := elem; + end with; + + \* In this specification, we need to model the data write, to make sure + \* that writes here doesn't actually affect the reads. This is similar to + \* the copy constructor that's called when the original code's + \* changeBiquadParameters is called. + WriterWriteObject: + while write_index <= kObjectSize do + assert g_memory[writer_local_pointer] # EMPTY; + g_memory[writer_local_pointer][write_index] := chosen_element; + WriterWriteObjectLoopInc: + write_index := write_index + 1; + end while; + + WriterWaitForNotInUse: + await g_in_use = FALSE; + + WriterSwapPointer: + writer_local_pointer := g_storage_pointer || g_storage_pointer := writer_local_pointer; + + WriterDeletePointer: + assert g_memory[writer_local_pointer] # EMPTY; + g_memory[writer_local_pointer] := EMPTY; +end process; + +end algorithm; *) +\* BEGIN TRANSLATION (chksum(pcal) = "18aa7fde" /\ chksum(tla) = "c8185e08") +CONSTANT defaultInitValue +VARIABLES g_memory, g_storage_pointer, g_in_use, pc, reader_local_pointer, + read_index, local_data, writer_local_pointer, chosen_element, + write_index + +vars == << g_memory, g_storage_pointer, g_in_use, pc, reader_local_pointer, + read_index, local_data, writer_local_pointer, chosen_element, + write_index >> + +ProcSet == {"reader"} \cup {"writer"} + +Init == (* Global variables *) + /\ g_memory = [i \in 1..kMemoryCapacity |-> IF i = 1 THEN DefaultObject ELSE EMPTY] + /\ g_storage_pointer = 1 + /\ g_in_use = FALSE + (* Process reader *) + /\ reader_local_pointer = defaultInitValue + /\ read_index = 1 + /\ local_data = DefaultObject + (* Process writer *) + /\ writer_local_pointer = defaultInitValue + /\ chosen_element = defaultInitValue + /\ write_index = 0 + /\ pc = [self \in ProcSet |-> CASE self = "reader" -> "ReaderSetInUseTrue" + [] self = "writer" -> "WriterAllocateObject"] + +ReaderSetInUseTrue == /\ pc["reader"] = "ReaderSetInUseTrue" + /\ g_in_use' = TRUE + /\ pc' = [pc EXCEPT !["reader"] = "ReaderReadPointer"] + /\ UNCHANGED << g_memory, g_storage_pointer, + reader_local_pointer, read_index, + local_data, writer_local_pointer, + chosen_element, write_index >> + +ReaderReadPointer == /\ pc["reader"] = "ReaderReadPointer" + /\ reader_local_pointer' = g_storage_pointer + /\ pc' = [pc EXCEPT !["reader"] = "ReaderCopyObject"] + /\ UNCHANGED << g_memory, g_storage_pointer, g_in_use, + read_index, local_data, + writer_local_pointer, chosen_element, + write_index >> + +ReaderCopyObject == /\ pc["reader"] = "ReaderCopyObject" + /\ IF read_index <= kObjectSize + THEN /\ Assert(g_memory[reader_local_pointer] # EMPTY, + "Failure of assertion at line 109, column 13.") + /\ local_data' = [local_data EXCEPT ![read_index] = g_memory[reader_local_pointer][read_index]] + /\ pc' = [pc EXCEPT !["reader"] = "ReaderReadObjectInc"] + ELSE /\ pc' = [pc EXCEPT !["reader"] = "ReaderSetInUseFalse"] + /\ UNCHANGED local_data + /\ UNCHANGED << g_memory, g_storage_pointer, g_in_use, + reader_local_pointer, read_index, + writer_local_pointer, chosen_element, + write_index >> + +ReaderReadObjectInc == /\ pc["reader"] = "ReaderReadObjectInc" + /\ read_index' = read_index + 1 + /\ pc' = [pc EXCEPT !["reader"] = "ReaderCopyObject"] + /\ UNCHANGED << g_memory, g_storage_pointer, g_in_use, + reader_local_pointer, local_data, + writer_local_pointer, chosen_element, + write_index >> + +ReaderSetInUseFalse == /\ pc["reader"] = "ReaderSetInUseFalse" + /\ g_in_use' = FALSE + /\ pc' = [pc EXCEPT !["reader"] = "Done"] + /\ UNCHANGED << g_memory, g_storage_pointer, + reader_local_pointer, read_index, + local_data, writer_local_pointer, + chosen_element, write_index >> + +reader == ReaderSetInUseTrue \/ ReaderReadPointer \/ ReaderCopyObject + \/ ReaderReadObjectInc \/ ReaderSetInUseFalse + +WriterAllocateObject == /\ pc["writer"] = "WriterAllocateObject" + /\ writer_local_pointer' = (CHOOSE i \in DOMAIN g_memory: g_memory[i] = EMPTY) + /\ g_memory' = [g_memory EXCEPT ![writer_local_pointer'] = DefaultObject] + /\ pc' = [pc EXCEPT !["writer"] = "WriterChooseObjectElement"] + /\ UNCHANGED << g_storage_pointer, g_in_use, + reader_local_pointer, read_index, + local_data, chosen_element, + write_index >> + +WriterChooseObjectElement == /\ pc["writer"] = "WriterChooseObjectElement" + /\ \E elem \in ObjectElements: + chosen_element' = elem + /\ pc' = [pc EXCEPT !["writer"] = "WriterWriteObject"] + /\ UNCHANGED << g_memory, g_storage_pointer, + g_in_use, reader_local_pointer, + read_index, local_data, + writer_local_pointer, write_index >> + +WriterWriteObject == /\ pc["writer"] = "WriterWriteObject" + /\ IF write_index <= kObjectSize + THEN /\ Assert(g_memory[writer_local_pointer] # EMPTY, + "Failure of assertion at line 156, column 13.") + /\ g_memory' = [g_memory EXCEPT ![writer_local_pointer][write_index] = chosen_element] + /\ pc' = [pc EXCEPT !["writer"] = "WriterWriteObjectLoopInc"] + ELSE /\ pc' = [pc EXCEPT !["writer"] = "WriterWaitForNotInUse"] + /\ UNCHANGED g_memory + /\ UNCHANGED << g_storage_pointer, g_in_use, + reader_local_pointer, read_index, + local_data, writer_local_pointer, + chosen_element, write_index >> + +WriterWriteObjectLoopInc == /\ pc["writer"] = "WriterWriteObjectLoopInc" + /\ write_index' = write_index + 1 + /\ pc' = [pc EXCEPT !["writer"] = "WriterWriteObject"] + /\ UNCHANGED << g_memory, g_storage_pointer, + g_in_use, reader_local_pointer, + read_index, local_data, + writer_local_pointer, + chosen_element >> + +WriterWaitForNotInUse == /\ pc["writer"] = "WriterWaitForNotInUse" + /\ g_in_use = FALSE + /\ pc' = [pc EXCEPT !["writer"] = "WriterSwapPointer"] + /\ UNCHANGED << g_memory, g_storage_pointer, g_in_use, + reader_local_pointer, read_index, + local_data, writer_local_pointer, + chosen_element, write_index >> + +WriterSwapPointer == /\ pc["writer"] = "WriterSwapPointer" + /\ /\ g_storage_pointer' = writer_local_pointer + /\ writer_local_pointer' = g_storage_pointer + /\ pc' = [pc EXCEPT !["writer"] = "WriterDeletePointer"] + /\ UNCHANGED << g_memory, g_in_use, reader_local_pointer, + read_index, local_data, chosen_element, + write_index >> + +WriterDeletePointer == /\ pc["writer"] = "WriterDeletePointer" + /\ Assert(g_memory[writer_local_pointer] # EMPTY, + "Failure of assertion at line 169, column 9.") + /\ g_memory' = [g_memory EXCEPT ![writer_local_pointer] = EMPTY] + /\ pc' = [pc EXCEPT !["writer"] = "Done"] + /\ UNCHANGED << g_storage_pointer, g_in_use, + reader_local_pointer, read_index, + local_data, writer_local_pointer, + chosen_element, write_index >> + +writer == WriterAllocateObject \/ WriterChooseObjectElement + \/ WriterWriteObject \/ WriterWriteObjectLoopInc + \/ WriterWaitForNotInUse \/ WriterSwapPointer + \/ WriterDeletePointer + +(* Allow infinite stuttering to prevent deadlock on termination. *) +Terminating == /\ \A self \in ProcSet: pc[self] = "Done" + /\ UNCHANGED vars + +Next == reader \/ writer + \/ Terminating + +Spec == Init /\ [][Next]_vars + +Termination == <>(\A self \in ProcSet: pc[self] = "Done") + +\* END TRANSLATION + +============================================================================= +\* Modification History +\* Last modified Sat Mar 23 13:21:24 EDT 2024 by shuhao +\* Created Sat Mar 23 11:26:21 EDT 2024 by shuhao diff --git a/tla/cas_exchange_loop/wrong/cas_exchange_loop_wrong.toolbox/cas_exchange_loop_wrong___SmallModel.launch b/tla/cas_exchange_loop/wrong/cas_exchange_loop_wrong.toolbox/cas_exchange_loop_wrong___SmallModel.launch new file mode 100644 index 0000000..a889eaf --- /dev/null +++ b/tla/cas_exchange_loop/wrong/cas_exchange_loop_wrong.toolbox/cas_exchange_loop_wrong___SmallModel.launch @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +