Split a TLA+ action into two sequential actions by introducing a new program counter (pc) state...
Split an existing TLA+ action into two sequential actions by introducing a new intermediate pc state. This is useful for modeling finer-grained atomicity or adding intermediate steps to an action sequence.
pc variable (or equivalent like state, phase) that tracks action sequencing. If not, create one.pc to transition between statesGiven an action like:
ActionX_1(thread) ==
/\ pc[thread] = PC_ActionX_1
/\ pc' = [pc EXCEPT ![thread] = PC_Start]
/\ y' = y + 2
/\ UNCHANGED <<x, z>>
Splitting it creates:
ActionX_1(thread) ==
/\ pc[thread] = PC_ActionX_1
/\ pc' = [pc EXCEPT ![thread] = PC_ActionX_2] \* Leads to new intermediate state
/\ y' = y + 2 \* Original logic (or subset per user)
/\ UNCHANGED <<x, z>>
ActionX_2(thread) == \* New action
/\ pc[thread] = PC_ActionX_2
/\ pc' = [pc EXCEPT ![thread] = PC_Start] \* Original destination
/\ UNCHANGED <<x, y, z>> \* All vars unchanged (or subset per user)
pc variable if not presentIf the specification lacks a pc variable, first use the "tlaplus-add-variable" skill to add pc variable. If the actions are thread- or process-specific, pc variable must be a function of the thread or process identifier to a string constant. For example,
PC_Begin == "PC_Begin"
PC_ActionX_1 == "PC_ActionX_1"
...
Init ==
/\ pc = [thread \in Threads |-> PC_Begin]
/\ ...
Read the specification and identify:
pc guard (entry state)pc' assignment (exit state)_0, _1, etc.)Follow the specification's naming convention:
If actions are numbered (e.g., ActionX_0, ActionX_1):
Example: Splitting ActionX_1 which goes to PC_Start:
PC_ActionX_2ActionX_1 now leads to PC_ActionX_2ActionX_2 leads to PC_StartIf actions have descriptive names (e.g., PC_Fetch, PC_Execute):
Add the new PC state to the PCStates set (or equivalent):
\* Before:
PCStates == {
PC_Ready,
PC_ActionX_1,
...
}
\* After:
PCStates == {
PC_Ready,
PC_ActionX_1, PC_ActionX_2, \* Added PC_ActionX_2
...
}
Update the original action to:
pc' to transition to the NEW intermediate stateWithout user instructions (empty intermediate action):
\* Before:
ActionX_1(thread) ==
/\ pc[thread] = PC_ActionX_1
/\ pc' = [pc EXCEPT ![thread] = PC_Start]
/\ y' = y + 2
/\ UNCHANGED <<x, z>>
\* After:
ActionX_1(thread) ==
/\ pc[thread] = PC_ActionX_1
/\ pc' = [pc EXCEPT ![thread] = PC_ActionX_2]
/\ y' = y + 2
/\ UNCHANGED <<x, z>>
ActionX_2(thread) == ... will be created in the next step ...
With user instructions (specific split):
If user specifies which variables to update in first vs second action, follow those instructions:
\* User says: "Update x in first action, y in second"
\* Before:
ActionX_1(thread) ==
/\ pc[thread] = PC_ActionX_1
/\ pc' = [pc EXCEPT ![thread] = PC_Start]
/\ x' = x + 1
/\ y' = y + 2
/\ UNCHANGED <<z>>
\* After:
ActionX_1(thread) ==
/\ pc[thread] = PC_ActionX_1
/\ pc' = [pc EXCEPT ![thread] = PC_ActionX_2]
/\ x' = x + 1 \* User-specified update in first action
/\ UNCHANGED <<y, z>>
ActionX_2(thread) == ... will be created in the next step ...
Create the new action that:
ActionX_2(thread) == \* New action
/\ pc[thread] = PC_ActionX_2
/\ pc' = [pc EXCEPT ![thread] = PC_Start] \* Original destination
/\ y' = y + 2 \* Logic from original (or per user)
/\ UNCHANGED <<x, z>>
If the naming convention uses numbers and there are subsequent actions that would conflict:
Before splitting:
ActionX_0(thread) == ... \* PC_ActionX_0 -> PC_ActionX_1
ActionX_1(thread) == ... \* PC_ActionX_1 -> PC_Start (TO BE SPLIT)
After splitting:
ActionX_0(thread) == ... \* PC_ActionX_0 -> PC_ActionX_1 (unchanged)
ActionX_1(thread) == ... \* PC_ActionX_1 -> PC_ActionX_2 (new destination)
ActionX_2(thread) == ... \* PC_ActionX_2 -> PC_Start (NEW ACTION)
If splitting ActionX_0 where ActionX_1 already exists, renumber:
Before:
ActionX_0(thread) == ... \* PC_ActionX_0 -> PC_ActionX_1 (TO BE SPLIT)
ActionX_1(thread) == ... \* PC_ActionX_1 -> PC_Start
After:
ActionX_0(thread) == ... \* PC_ActionX_0 -> PC_ActionX_1 (NEW intermediate)
ActionX_1(thread) == ... \* PC_ActionX_1 -> PC_ActionX_2 (renumbered from old ActionX_1)
ActionX_2(thread) == ... \* PC_ActionX_2 -> PC_Start (renumbered)
If TypeOk contains PCStates enumeration, add the new PC state:
\* Before:
TypeOk ==
/\ pc \in [Threads -> PCStates]
...
\* If PCStates is defined separately, it was updated in Step 3.
\* If PCStates is inline, update it here.
If the Next predicate explicitly lists actions (not common), add the new action:
Next == \E thread \in Threads :
\/ ActionX_0(thread)
\/ ActionX_1(thread)
\/ ActionX_2(thread) \* Add new action
...
If actions are grouped (e.g., ActionX(thread) with internal disjunctions), the new disjunct is already included.
If fairness constraints reference specific PC states or actions, update them:
\* Before:
Fairness ==
/\ WF_vars(ActionX_1(thread))
\* After - add fairness for new action:
Fairness ==
/\ WF_vars(ActionX_1(thread))
/\ WF_vars(ActionX_2(thread))
When splitting an action, follow these rules for UNCHANGED:
First action (intermediate step):
Second action (new action):
Never forget variables: Every variable must either be primed (updated) or in UNCHANGED
After splitting, verify:
User request: "Split ActionX_1 into two actions"
Before:
PC_ActionX_1 == "PC_ActionX_1"
PCStates == { PC_Ready, PC_ActionX_1 }
ActionX_0(thread) ==
/\ pc[thread] = PC_Start
/\ pc' = [pc EXCEPT ![thread] = PC_ActionX_1]
/\ x' = x + 1
/\ UNCHANGED <<y, z>>
ActionX_1(thread) ==
/\ pc[thread] = PC_ActionX_1
/\ pc' = [pc EXCEPT ![thread] = PC_Start]
/\ y' = y + 2
/\ UNCHANGED <<x, z>>
After:
PC_ActionX_1 == "PC_ActionX_1"
PC_ActionX_2 == "PC_ActionX_2" \* NEW
PCStates == { PC_Ready, PC_ActionX_1, PC_ActionX_2 } \* Updated
ActionX_0(thread) ==
/\ pc[thread] = PC_Start
/\ pc' = [pc EXCEPT ![thread] = PC_ActionX_1]
/\ x' = x + 1
/\ UNCHANGED <<y, z>>
ActionX_1(thread) ==
/\ pc[thread] = PC_ActionX_1
/\ pc' = [pc EXCEPT ![thread] = PC_ActionX_2] \* Changed destination
/\ UNCHANGED <<x, y, z>> \* All vars unchanged
ActionX_2(thread) == \* NEW ACTION
/\ pc[thread] = PC_ActionX_2
/\ pc' = [pc EXCEPT ![thread] = PC_Start]
/\ y' = y + 2
/\ UNCHANGED <<x, z>>