Skip to content

[Formal][Property Annotation] Annotating Exit Token Invariants#922

Open
Basmet0 wants to merge 32 commits into
EPFL-LAP:mainfrom
Basmet0:invariant10
Open

[Formal][Property Annotation] Annotating Exit Token Invariants#922
Basmet0 wants to merge 32 commits into
EPFL-LAP:mainfrom
Basmet0:invariant10

Conversation

@Basmet0
Copy link
Copy Markdown
Collaborator

@Basmet0 Basmet0 commented May 18, 2026

An exit token - meaning a token that decides whether a loop exits or continues - can only occur once. There will be other decider tokens before an exit token, and, given that there is an exit token, no ancestors of the decider will contain a token.

Bas Niekel added 27 commits April 30, 2026 19:04
1. Within an IOG, find all deciding branches (between looping or never coming back)
2. Find the decider operation making the condition
3. Search forward for all branches coming out of the decider. These are all exit branches.
@Basmet0 Basmet0 changed the base branch from main to invariant8 May 19, 2026 14:51
@Basmet0 Basmet0 changed the base branch from invariant8 to main May 21, 2026 08:34
@Basmet0 Basmet0 changed the title [Formal][Property Annotation] Annotating invariants 7,8,9,10 [Formal][Property Annotation] Annotating Exit Token Invariants May 21, 2026
@Basmet0 Basmet0 requested a review from Jiahui17 May 21, 2026 08:39
Comment on lines +608 to +627
struct BranchOpDecision {
// true -> this branch loops towards itself
// false -> this branch exits
bool trueLoop;
bool falseLoop;

inline bool isExitBranch() { return trueLoop != falseLoop; }
};

BranchOpDecision findBranchLoops(const IOG &iog, ConditionalBranchOp branch) {
BranchOpDecision ret;
Operation *opTrue = *branch.getTrueResult().getUsers().begin();
auto paths = iog.findAllPaths(opTrue, branch);
ret.trueLoop = !(paths.units.find(branch) == paths.units.end());

Operation *opFalse = *branch.getFalseResult().getUsers().begin();
paths = iog.findAllPaths(opFalse, branch);
ret.falseLoop = !(paths.units.find(branch) == paths.units.end());
return ret;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestions:

  1. Maybe just call it isIOGLoopExitBranch(..) (returns a Boolean value instead)? We can omit the new custom type in that case
  2. Could be declared as a class method of IOG

Comment on lines +629 to +651
Operation *getDecider(ConditionalBranchOp branch) {
std::vector<mlir::Value> stack;
stack.push_back(branch.getConditionOperand());
Operation *dec = branch;
while (!stack.empty()) {
mlir::Value cur = stack.back();
stack.pop_back();
Operation *op = cur.getDefiningOp();

if (!op) {
continue;
}
dec = op;

if (auto forkOp = dyn_cast<ForkOp>(op)) {
stack.push_back(forkOp.getOperand());
}
if (auto buffer = dyn_cast<BufferOp>(op)) {
stack.push_back(buffer.getOperand());
}
}
return dec;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestions:

  1. // a decider is ....
  2. we could return a std::optional<Operation *> (or llvm::FailureOr<Operation*>) and encode "we didn't find a decider" as nullopt
  3. The parent function returns failure() if no decider was discovered
  4. use else if

For instance, if op is somehow a nullptr we should return a nullopt

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this from the PR

iogBranch = slots.end;
}
}
assert(iogBranch);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return failure()

Operation *dec = *getDecider(branch);
deciders.insert({dec, exitValue});
}
for (auto [dec, exitValue] : deciders) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop body here is quite long, i think it makes sense to put some markers to visually separate the code for different invariants:

// [START Enumerating ExitTokenOrder invariants]
 for (const auto &slots : slotPaths) {
 ...
   propertyTable.push_back(p.toJSON());
 }
// [END Enumerating ExitTokenOrder invariants]

// [START Enumerating ExitTokenNoAncestors invariants]
// [END Enumerating ExitTokenNoAncestors invariants]

inline static const StringLiteral PATH_CM_LIT = "cmerge_mux_path";
};

// In a path between a decider unit and a branch, only the first token along the
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

I think it helps to include an example like this in the comment to make the description easier to understand


Also the UnstartedPath can reference this diagram/example


BTW Slot followed by any number of EForks can be shortened with EffectiveSlot

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW the descriptions you added there are informative, it is just that the thing we want to describe is generally quite convoluted and we want to visualize this part for the reader

}

Operation *op = cur.channel.getDefiningOp();
if (!op || !iog.contains(op) || op == exit ||
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestions

  1. Order the cases as generating more returns paths (each input of a multi-input unit), populating one return path (Buffer), and terminination condition
  2. Explain the termination condition in more detail?
  3. Use if .. else if

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. In this function, the operations are not handled case-by-case, but instead using their interfaces as any operation is allowed here. The order cannot be changed without breaking the behaviour:
    First, terminate in bad cases (otherwise potentially too many things are annotated).
    Second, if applicable, an eager fork output is added as a copied sent to the next slot.
    Third, slots are annotated.
    Lastly, all inputs are followed backwards (regardless of the number of inputs, can be 1 for buffer or fork, or 2 for arithmetic operations or loads)
    I added some comments to explain this in code as well

  2. Yes, comment added

  3. Similar to 1., if .. else if does not make sense here as I am not handling different cases but instead following the same four steps for any operation.

Comment on lines +715 to +716
std::vector<EffectiveSlotNamer> backPath;
std::vector<EagerForkSentNamer> backSents;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forward vs. backward: conceptually which one do you call forward?

stack.pop_back();

Operation *next = *path.cur.getUsers().begin();
if (auto branch = dyn_cast<ConditionalBranchOp>(next)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same suggestions as the other similar loop:

Suggestions

  1. Order the cases as generating more returns paths (each input of a multi-input unit), populating one return path (Buffer), and terminination condition
  2. Explain the termination condition in more detail?
  3. Use if .. else if

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here that does make sense yes

}
}

// Paths from decider to the branch might contain a fork before they contain a
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remember to backreference the diagram I added in the other comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants