This is a tricky one. Here's my most minimal test case, which is a cut down version of "Fuzz testing: Parser24" in parserTests.txt:
{{<u {{{{[[Sx-->}}
The output of the PHP and JS tokenizer differs -- the "correct" parse is:
<p>{{<u>}}</u></p>
That is, all the crazy stuff after <u and before the > is parsed as a very strange attribute name, which is later dropped. The {{ at the very start and the }} at the very end are treated as literal text, via the broken_template rule.
Turning off all caching (by hacking phpCacheRuleHook and jsCacheRuleHook to return '') results in the correct value on both PHP and JS. With caching enabled, JS still creates the correct result -- but that appears to be because it bypasses the cache unless visit count is 20 or greater, and our test case is short. PHP outputs an incorrect result (the <u> is parsed as a broken tag, so it comes out <u), because its simpler cache implementation always checks the cache When you change maxVisitCount to -1 in tokenizer.js to make the JS port always check the cache, JS also emits this incorrect result..
I *believe* what's going on is that the cache is missing some aspect of the parser rule variables, so it's reusing a previous result which was cached with a different version of the variable state. @tstarling appears to have a pretty sophisticated mechanism for including rule variable state into the cache key, but somehow it's missing something.
(As an alternative to this mechanism, we could create a new cache when a parser rule value is updated, and then when the value is restored when the rule is exited we can restore the old cache. Reference variables cause a little havoc, but it's probably fine just to flush all the caches when they are updated since that doesn't happen very often (only triggered by broken templates). But let me figure out where exactly the bug is lurking, first...)