Анимация
JavaScript
|
Главная Библионтека Самая явная оплошность в том, что он неправильный! Если вы оглянетесь на код для вызова процедур, вы увидите, что вызывающая подпрограмма помещает каждый фактический параметр в стек перед тем, как она вызывает процедуру. Процедура использует эту информацию, но она не изменяет указатель стека. Это означает, что содержимое все еще остается там когда мы возвращаемся. Кто-то должен очистить стек или мы скоро окажемся в очень трудной ситуации! К счастью, это легко исправить. Все, что мы должны сделать - это увеличить указатель стека когда мы закончим. Должны ли мы делать это в вызывающей программе или в вызываемой процедуре? Некоторые люди позволяют вызываемой процедуре очищать стек, так как требуется генерировать меньше кода на вызов и так как процедура, в конце концов, знает сколько параметров она получила. Но это означает, что она должна что-то делать с адресом возврата чтобы не потерять его. Я предпочитаю разрешить очистку в вызывающей программе, так что вызываемая процедура должна только выполнить возврат. Также это кажется немного более сбалансированным так как именно вызывающая программа первой "засорила" стек. Но это означает, что вызывающая программа должна запоминать сколько элементов помещено в стек. Чтобы сделать проще, я изменил процедуру ParamList на функцию, возвращающую количество помещенных байт: { Process the Parameter List for a Procedure Call } function ParamList: integer; var N: integer; begin N := 0; MatchCC); if Look <> ) then begin Param; inc(N); while Look = , do begin MatchC,); Param; inc(N); end; end; Match()); ParamList := 2 * N; end; Процедура 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 EmitCADD #); WriteLn(N, ,SP); end; end; ОК, если вы добавили этот код в ваш компилятор, я думаю вы убедитесь, что стек теперь под контролем. Следующая проблема имеет отношение к нашему способу адресации относительно указателя стека. Это работает отлично на наших простых примерах, так как с нашей элементарной формой выражений никто больше не засоряет стек. Но рассмотрим другой пример, такой простой как: PROCEDURE FOO(A, B) BEGIN A = A + B Код, сгенерированный нехитрым синтаксическим анализатором, мог бы быть: FOO: MOVE 6(SP),D0 ; Извлечь A Сохранить ег Извлечь B Добавить A Сохранить A MOVE D0,-(SP) MOVE 4(SP),D0 ADD (SP)+,D0 MOVE D0,6(SP) 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 Исправить компилятор для генерации этого кода намного проще чем объяснить. Все, что нам нужно сделать - изменить генерацию кода в DoProc. Так как из-за этого код становится немного больше одной строки, я создал новые процедуры, схожие с процедурами Prolog и Epilog, вызываемыми DoMain: { Write the Prolog for a Procedure } procedure ProcProlog(N: char); begin PostLabel(N); EmitLnCLINK A6,#0); end; { Write the Epilog for a Procedure } procedure ProcEpilog; begin EmitLnCUNLK 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: { Load a Parameter to the Primary Register } procedure LoadParam(N: integer); var Offset: integer; begin Offset := 8 + 2 * (NumParams - N); EmitCMOVE ); WriteLn(Offset, (A6),D0); 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 [ 74 ] 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |