Youāre right, the second function is argmemonly. I jumped ahead to trying to create a sound rule, but itās not an exact one. The actual underlying issue I was getting at is this: You need to ensure that other functions which are called transitively also only access memory passed as argument to the function you want to mark as argmemonly. For example, if foo calls bar and bar accesses global memory, foo canāt be argmemonly.
The reason for that is: Generally, a property like āthe function does not do Xā means that āduring the execution of the function, X does not happenā. Put differently, it doesnāt matter whether X happens literally in the function itself or is delegated to another function ā the effect from the callerās point of view is the same. Thatās because the callerās point of view is what matters if you want to optimize a call to the function that is claimed to not do X, and also because function calls may be inlined.
Well, after youāve optimized the former to the latter, they are equivalent. The distinction only exists if you analyze the source program without any simplification. One optimization allowing further optimizations that were hampered by the code that was optimized away is a common theme.