Section 5.2.3, regarding the semantics of call and return, focuses exclusively on procedure calls that show up as statements, rather than paying any attention to procedure calls used as expressions. That's a good thing, because Tucker and Noonan's meaning function for expressions cannot be extended in any incremental way to accommodate procedure calls. Why not? (Recall that M(Expression e, State σ) is defined as the value computed by e when evaluated in σ.) Having noted this issue, we will continue to only consider statements.
The semantics in this section is fundamentally flawed in that it does not recognize that the environment and memory components of states play very different roles. The memory component follows the temporal order of execution. As such, the memory when a procedure invocation starts needs to reflect the state at the point of the procedure call, and the memory when the call is over needs to reflect the state at the end of the procedure invocation. In contrast to this, the environments need to track static scoping, not execution order. The environment for execution of the procedure body does not incorporate the environment at the point of call.
In order to remedy this problem (and other more minor technical difficulties), we can revise the definition of activate. (As before, let's leave out the static and dynamic links.)
activate(m, c.args, σ) =
(γm, μm, am)
where σ = (γ, μ, a)
and (γm, μa,
am) = allocate(m.params,
m.locals, (γg, μ, a))
and μm = μa ∪ {‹m.paramsi,
M(c.argsi, σ)› | i∈{1,...,np}}.
Notice that the calling environment, γ, plays a role only in the evaluation of the arguments (as a component of the state σ). It does not feed into the method body's environment, γm, which allocate forms by adding to the global environment, γg.
Having fixed this problem regarding proceure calling, we also need to make a corresponding change to the way procedure return is modeled, so that the environment after the procedure call reverts back to being the same as before the call. This can be achieved by changing the main equation for the meaning of a call:
M(Call s, State σ) = (γ,
μ', a')
where σ = (γ, μ, a)
and (γ', μ', a') = deactivate(s.name,
M(s.name.body, activate(s.name, s.args, σ))).
In this definition, the primed variables refer to the state after executing the procedure body. Notice that the state produced by the M function includes the original environment, γ, not the modified environment γ'. (It should always be the case that a=a', because deallocating the stack frame exactly balances out allocating it. I have used two different names to distinguish the values of the stack pointer before and after the call, even though they are equal.)
Proceding with the book's illustrative example, the environments in which methods A and B are evaluated should be calculated as follows:
γA = γg ∪
{‹x, 4›, ‹y, 5›,
‹i, 6›, ‹j, 7›}
= {‹h, 0›,
‹x, 4›, ‹y, 5›, ‹i, 6›, ‹j, 7›}.
γB = γg ∪
{‹w, 8›, ‹j, 9›,
‹k, 10›}
=
{‹h, 0›, ‹i, 1›, ‹w, 8›, ‹j, 9›,
‹k, 10›}.