Процедура CallProc затем использует его для очистки стека:
{–}
{ Process a Procedure Call }
procedure CallProc(Name: char);
var N: integer;
begin
N := ParamList;
Call(Name);
CleanStack(N);
end;
{–}
Здесь я создал еще одну подпрограмму генерации кода:
{–}
{ Adjust the Stack Pointer Upwards by N Bytes }
procedure CleanStack(N: integer);
begin
if N > 0 then begin
Emit('ADD #');
WriteLn(N, ',SP');
end;
end;
{–}
ОК, если вы добавили этот код в ваш компилятор, я думаю вы убедитесь, что стек теперь под контролем.
Следующая проблема имеет отношение к нашему способу адресации относительно указателя стека. Это работает отлично на наших простых примерах, так как с нашей элементарной формой выражений никто больше не засоряет стек. Но рассмотрим другой пример, такой простой как:
PROCEDURE FOO(A, B)
BEGIN
A = A + B
END
Код, сгенерированный нехитрым синтаксическим анализатором, мог бы быть:
FOO: MOVE 6(SP),D0 ; Извлечь A
MOVE D0,-(SP) ; Сохранить его
MOVE 4(SP),D0 ; Извлечь B
ADD (SP)+,D0 ; Добавить A
MOVE D0,6(SP) : Сохранить A
RTS
Это было бы неправильно. Когда мы помещаем первый аргумент в стек, смещения для двух формальных параметров больше не 4 и 6, я 6 и 8. Поэтому вторая выборка вернула бы снова A а не B.
Но это не конец света. Я думаю, вы можете видеть, что все, что мы должны делать – изменять смещение каждый раз, когда мы помещаем в стек и что фактически и делается если ЦПУ не имеет поддержки других методов.
К счастью, все-же, 68000 имеет такую поддержку. Поняв, что этот ЦПУ мог бы использоваться со многими компиляторами языков высокого уровня, Motorola решила добавить прямую поддержку таких вещей.
Проблема, как вы можете видеть в том, что когда процедура выполняется, указатель стека скачет вверх и вниз, и поэтому использование его как ссылки для доступа к формальным параметрам становится неудобным. Решение состоит в том, чтобы вместо него определить и использовать какой-то другой регистр. Этот регистр обычно устанавливается равным подлинному указателю стека и называется указателем кадра.
Команда LINK из набора инструкций 68000 позволяет вам объявить такой указатель кадра и установить его равным указателю стека и все это в одной команде. Фактически, она делает даже больше чем это. Так как этот регистр может использоваться для чего-то еще в вызывающей процедуре, LINK также помещает текущее значение регистра в стек. Вы можете также добавить значение к указателю стека чтобы создать место для локальных переменных.
В дополнение к LINK есть UNLK, которая просто восстанавливает указатель стека и выталкивает старое значение обратно в регистр.
С использованием этих двух команд код для предыдущего примера станет:
FOO: LINK A6,#0
MOVE 10(A6),D0 ; Извлечь A
MOVE D0,-(SP) ; Сохранить его
MOVE 8(A6),D0 ; Извлечь B
ADD (SP)+,D0 ; Добавить A
MOVE D0,10(A6) : Сохранить A
UNLK A6
RTS
Исправить компилятор для генерации этого кода намного проще чем объяснить. Все, что нам нужно сделать – изменить генерацию кода в DoProc. Так как из-за этого код становится немного больше одной строки, я создал новые процедуры, схожие с процедурами Prolog и Epilog, вызываемыми DoMain:
{–}
{ Write the Prolog for a Procedure }
procedure ProcProlog(N: char);
begin
PostLabel(N);
EmitLn('LINK A6,#0');
end;
{–}
{ Write the Epilog for a Procedure }
procedure ProcEpilog;
begin
EmitLn('UNLK A6');
EmitLn('RTS');
end;
{–}
Процедура DoProc теперь просто вызывает их:
{–}
{ Parse and Translate a Procedure Declaration }
procedure DoProc;
var N: char;
begin
Match('p');
N := GetName;
FormalList;
Fin;
if InTable(N) then Duplicate(N);
ST[N] := 'p';
ProcProlog(N);
BeginBlock;
ProcEpilog;
ClearParams;
end;
{–}
В заключение, мы должны изменить ссылки на SP в процедурах LoadParam и StoreParam: