I am working on this problem right now - i.e. analytical computation of stacksize.
It is clearly going to be a highly recursive piece of code, because a function call could have an indexed array as one or more of its arguments and one or more of the array indices could involve function calls!!
However, a couple of realizations allow some relief from the complexity:
(1) When using a high-level language compiler, the stackpointer at the end of execution of each statement/line-of-code should be at the same place as it was at the start.
(At least this would be a good rule to observe otherwise you are going to have problems!)
(2)The stackpointer after returning from each function or subroutine call should be the same as it was pre-call. Therefore the maximum stack size is the maximum, over all statements in the program, of the peak stacksize reached in each statement.
(At least this would be a good rule to observe otherwise you are going to have problems!)
Of course a statement can include the recursive issues I mentioned above, but at least the problem of finding the maximum stacksize requirement of a whole program then boils down to finding the maximum stacksize requirement of each statement, and then picking the maximum of those.
This cannot be completed until all functions called have also been compiled. So I generate a file for each module compiled that records a number of stacksizes for each statement
(basically the peak value prior to each function call and the value immmediately prior to each function call (excluding any as yet unknown additions to stacksize caused by the function call), and the function names involved. Then I retrospectively process these files using a recursive routine, once all functions have been compiled, to determine peak stacksize.
The fortunate thing is that, recursive routines apart, the maximum possible stacksize requirement does not then depend on program flow, although in a typical flow (which is data dependent) this maximum possible stacksize may never be reached.
Example: Suppose function 1 calls function 2 and the program flow of both depends on a data value X. Suppose there is a range of X which causes function 1 to execute its worst statement, which includes a call to function 2, which does not execute its worst case statement for that same range of X. Since we computed the maximum possible stacksize by using the worst cases for both function 1 and function 2 simultaneously, we may have overestimated the stacksize. At least we erred on the safe side.
I like to give interrupt routines their own stackspace on an OS stack if they need any, so they do not add to program stack requirements, apart from the return-from-interrupt address