ComputeValueKnownInPredecessors has a "visited" set to prevent infinite loops, since a value can be visited more than once. However, the implementation didn't prevent the algorithm from taking exponential time. Instead of removing elements from the RecursionSet one at a time, we should keep around the whole set until ComputeValueKnownInPredecessors finishes, then discard it.
The testcase is synthetic because I was having trouble effectively reducing the original. But it's basically the same idea.
Instead of failing, we could theoretically cache the result instead. But I don't think it would help substantially in practice.