Open subroutines -...

13
Open subroutines Идея открытой подпрограммы Приведенный ниже фрагмент кода реализует вычисление абсолютного значения 1 (модуля) числа, находящегося в ячейке с адресом 200, и запись результата в ячейку 201. Предполагается, что используется загрузчик Initial Orders 1, и инструкции фрагмента размещаются в ячейках 50-55 2 . 50: T 201 S ; Mem[201] = Acc, Acc = 0 51: A 200 S ; Acc = Acc + Mem[200] (= Mem[200]) 52: E 55 S ; if( Acc >= 0 ) goto 55 53: T 201 S ; Mem[201] = Acc, Acc = 0 54: S 200 S ; Acc = Acc - Mem[200] (= - Mem[200]) 55: T 201 S ; Mem[201] = Acc (= |Mem[200]|) Операция вычисления модуля может применяться в различных программах (и даже несколько раз в одной программе). При этом, разумеется, изменяется как место нахождения в памяти данных (аргумента и результата), так и место нахождения в памяти инструкций, составляющих приведенный фрагмент. Учитывая это, разумно обобщить приведенный фрагмент, записав его в виде «параметризованного шаблона»: <Org>+0: T <Res> S ; Mem[<Res>] = Acc, Acc = 0 <Org>+1: A <Arg> S ; Acc = Acc + Mem[<Arg>] (= Mem[<Arg>]) <Org>+2: E <Org>+5 S ; if( Acc >= 0 ) goto <Org>+5 <Org>+3: T <Res> S ; Mem[<Res>] = Acc, Acc = 0 <Org>+4: S <Arg> S ; Acc = Acc - Mem[<Arg>] (= - Mem[<Arg>]) <Org>+5: T <Res> S ; Mem[<Res>] = Acc (= |Mem[<Arg>]|) При наличии этого шаблона реализация операции вычисления модуля в нужном месте программы становится тривиальной. Например, следующий фрагмент реализует вычисление суммы модулей двух чисел, находящихся в ячейках 300 и 305, и запись результата в ячейку 305. В качестве рабочей (промежуточной) используется ячейка 0. Предполагается, что инструкции фрагмента размещаются в памяти, начиная с адреса 72. 72: T 0 S ;\ 73: A 305 S ; } 74: E 77 S ; } “Шаблон” с <Org>=72, <Arg>=305, <Res>=0 75: T 0 S ; } Mem[0] = |Mem[305]| 76: S 305 S ; } 77: T 0 S ;/ 78: T 305 S ;\ 1 Операция вычисления абсолютного значения (модуля) тривиальна, однако – именно поэтому – ее удобно использовать в методических целях. Чтобы лучше «почувствовать» проблему, которая здесь решается, можно представить, что вместо операции «модуль» речь идет об операции «косинус». 2 Все адреса выбраны произвольно, в числах 200, 201, 50 (а также 72, 300, 305, см. ниже) нет никакого тайного смысла.

Transcript of Open subroutines -...

Page 1: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

Open subroutines

Идея открытой подпрограммы Приведенный ниже фрагмент кода реализует вычисление абсолютного значения1 (модуля) числа,

находящегося в ячейке с адресом 200, и запись результата в ячейку 201. Предполагается, что

используется загрузчик Initial Orders 1, и инструкции фрагмента размещаются в ячейках 50-552.

50: T 201 S ; Mem[201] = Acc, Acc = 0

51: A 200 S ; Acc = Acc + Mem[200] (= Mem[200])

52: E 55 S ; if( Acc >= 0 ) goto 55

53: T 201 S ; Mem[201] = Acc, Acc = 0

54: S 200 S ; Acc = Acc - Mem[200] (= - Mem[200])

55: T 201 S ; Mem[201] = Acc (= |Mem[200]|)

Операция вычисления модуля может применяться в различных программах (и даже несколько раз

в одной программе). При этом, разумеется, изменяется как место нахождения в памяти данных

(аргумента и результата), так и место нахождения в памяти инструкций, составляющих

приведенный фрагмент. Учитывая это, разумно обобщить приведенный фрагмент, записав его в

виде «параметризованного шаблона»:

<Org>+0: T <Res> S ; Mem[<Res>] = Acc, Acc = 0

<Org>+1: A <Arg> S ; Acc = Acc + Mem[<Arg>] (= Mem[<Arg>])

<Org>+2: E <Org>+5 S ; if( Acc >= 0 ) goto <Org>+5

<Org>+3: T <Res> S ; Mem[<Res>] = Acc, Acc = 0

<Org>+4: S <Arg> S ; Acc = Acc - Mem[<Arg>] (= - Mem[<Arg>])

<Org>+5: T <Res> S ; Mem[<Res>] = Acc (= |Mem[<Arg>]|)

При наличии этого шаблона реализация операции вычисления модуля в нужном месте

программы становится тривиальной. Например, следующий фрагмент реализует вычисление

суммы модулей двух чисел, находящихся в ячейках 300 и 305, и запись результата в ячейку 305. В

качестве рабочей (промежуточной) используется ячейка 0. Предполагается, что инструкции

фрагмента размещаются в памяти, начиная с адреса 72.

72: T 0 S ;\

73: A 305 S ; }

74: E 77 S ; } “Шаблон” с <Org>=72, <Arg>=305, <Res>=0

75: T 0 S ; } Mem[0] = |Mem[305]|

76: S 305 S ; }

77: T 0 S ;/

78: T 305 S ;\

1 Операция вычисления абсолютного значения (модуля) тривиальна, однако – именно поэтому – ее удобно

использовать в методических целях. Чтобы лучше «почувствовать» проблему, которая здесь решается, можно представить, что вместо операции «модуль» речь идет об операции «косинус». 2 Все адреса выбраны произвольно, в числах 200, 201, 50 (а также 72, 300, 305, см. ниже) нет никакого

тайного смысла.

Page 2: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

79: A 300 S ; }

80: E 83 S ; } “Шаблон” с <Org>=78, <Arg>=300, <Res>=305

81: T 305 S ; } Mem[305] = |Mem[300]|

82: S 300 S ; }

83: T 305 S ;/

84: A 305 S ; Acc = Acc + Mem[305] (= Mem[305])

85: A 0 S ; Acc = Acc + Mem[0]

86: T 305 S ; Mem[300] = Acc

Отметим следующее:

1) Использование «шаблона» состоит в его переписывании с подставленными значениями

параметров, в частности, начального адреса загрузки формируемого фрагмента кода.

2) Полученный код не является оптимальным, например:

- можно опустить инструкцию в ячейке 78 (т.к. аккумулятор уже обнуляется инструкцией

по адресу 77);

- изменив инструкцию в ячейке 83 на “U” (запись с сохранением значения

аккумулятора), можно опустить инструкцию в ячейке 84.

3) Следует учитывать, что используемый «шаблон» не допускает совпадения адресов

аргумента и результата, т.е. вычисление модуля “in-place” (ограничение вызвано

инструкцией <Org>+0); этим ограничением вызвана необходимость в дополнительной

рабочей ячейке памяти, а желание использовать только одну рабочую ячейку повлияло на

организацию кода, а именно порядок вычисления промежуточных значений (модулей).

4) В данном фрагмента использован тот факт, что после выполнения инструкций «шаблона»

значение аккумулятора равно 0 (в противном случае, перед выполнением инструкций в

ячейках 84-86 потребовалось бы предварительно обнулить аккумулятор).

5) При использовании полученного фрагмента следует иметь в виду следующие побочные

эффекты (side effects):

- фрагмент использует ячейку 0 в качестве рабочей, т.е. изменяет ее значение (а именно,

в ячейку 0 записывается модуль содержимого ячейки 305);

- в результате выполнения фрагмента кода изменяется (а именно, обнуляется)

содержимое аккумулятора.

Неудобства, отмеченные в п. 1, могут быть устранены использованием загрузчика Initial Orders 2 (в

этом, собственно, и состояла цель его создания). Как отметил в работе [1] автор загрузчика

D.J. Wheeler, использование «шаблона» является эффективной процедурой, следовательно, ее

выполнение можно поручить самой вычислительной машине.

Неоптимальность получаемого кода (п.2), по-видимому, является той ценой, которую требуется

заплатить за возможность многократного использования созданного один раз фрагмента без

необходимости его изменения и связанных с этим затрат времени на разработку и отладку.

Допустимость накладных расходов вычислительных ресурсов, сопряженных с повторным

использованием кода, оценивается (разработчиком программы) в каждом конкретном случае.

Особенности по пп.3-4 связаны с необходимостью подробного документирования фрагмента

кода, предназначенного для повторного использования. Так, очевидно, в описании «шаблона»

Page 3: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

должно быть указано, что значения параметров <Arg> и <Res> не могут совпадать. Не зная об этом

ограничении, логично было бы организовать код следующим образом:

а) рассчитывать модуль числа в ячейке 300, размещать результат в рабочей ячейке 03;

б) рассчитывать модуль числа в ячейке 305 in-place (т.е. размещать результат в ячейке 305);

в) прибавлять к значению ячейки 305 (п.б) значение рабочей ячейки (п.а).

В результате, разумеется, был бы получен некорректный код.

Если учет ограничения на <Arg> и <Res> необходим для правильного применения «шаблона», то

информация о том, что в результате выполнения инструкций «шаблона» аккумулятор обнуляется,

позволяет сделать код более эффективным (и более простым), как отмечено выше в п.4.

Содержание п.5 имеет отношение скорее к результирующему коду, т.е. к конкретному

применению «шаблона», чем к самому «шаблону».

То, что выше мы называли «шаблоном» в группе пользователей EDSAC называлось «открытой

подпрограммой» (open subroutine). В наше время такого рода конструкции называются

макросами (macro).

Реализация открытых подпрограмм с Initial Orders 2 Переход к использованию Initial Orders 2 начнем с незначительной модификации шаблона

(замены признака операций с короткими словами “S” на код (code letter) “F”):

<Org>+0: T <Res> F ; Mem[<Res>] = Acc, Acc = 0

<Org>+1: A <Arg> F ; Acc = Acc + Mem[<Arg>] (= Mem[<Arg>])

<Org>+2: E <Org>+5 F ; if( Acc >= 0 ) goto <Org>+5

<Org>+3: T <Res> F ; Mem[<Res>] = Acc, Acc = 0

<Org>+4: S <Arg> F ; Acc = Acc - Mem[<Arg>] (= - Mem[<Arg>])

<Org>+5: T <Res> F ; Mem[<Res>] = Acc (= |Mem[<Arg>]|)

Далее заметим, что при загрузке программы загрузчиком Initial Orders 2 адрес текущей целевой

ячейки может быть зафиксирован в ячейке 42 с помощью директивы (directive, «control

combination» в терминах Initial Orders 2) “GK”:

GK ; Mem[42]=<Org>

<Org>+0: T <Res> F ; Mem[<Res>] = Acc, Acc = 0

Теперь можно сделать текст «шаблона» независимым от начального адреса загрузки

формируемого фрагмента кода, воспользовавшись кодом “”4:

GK ; =Mem[42]=<Org>

+0: T <Res> F ; Mem[<Res>] = Acc, Acc = 0

+1: A <Arg> F ; Acc = Acc + Mem[<Arg>] (= Mem[<Arg>])

+2: E 5 ; if( Acc >= 0 ) goto <Org>+5

+3: T <Res> F ; Mem[<Res>] = Acc, Acc = 0

+4: S <Arg> F ; Acc = Acc - Mem[<Arg>] (= - Mem[<Arg>])

+5: T <Res> F ; Mem[<Res>] = Acc (= |Mem[<Arg>]|)

3 Или даже в самой ячейке 300, если ее значение в дальнейшем не требуется.

4 В EDSAC Simulator “” представляется символом “@”.

Page 4: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

Как можно видеть, использование кода “” избавляет пользователя подпрограммы, но не ее

разработчика, от необходимости подсчитывать адреса инструкций.

Наконец, воспользуемся общим механизмом параметров Initial Orders 2 (специальным случаем

которого является код “”). Будем использовать ячейку 45 (соответствует коду “H”) для значения

параметра <Arg>, а ячейку 46 (соответствует коду “N”) - для значения параметра <Res>:

GK ; Mem[42]=<Org>

0: T 0 N ; Mem[<Res>] = Acc, Acc = 0

1: A 0 H ; Acc = Acc + Mem[<Arg>] (= Mem[<Arg>])

2: E 5 ; if( Acc >= 0 ) goto <Org>+5

3: T 0 N ; Mem[<Res>] = Acc, Acc = 0

4: S 0 H ; Acc = Acc - Mem[<Arg>] (= - Mem[<Arg>])

5: T 0 N ; Mem[<Res>] = Acc (= |Mem[<Arg>]|)

Эквивалентная сокращенная запись этой подпрограммы:

GK

TN

AH

E5

TN

SH

TN

Можно видеть, что теперь код подпрограммы не зависит ни от начального адреса ее загрузки, ни

от адресов ячеек, содержащих аргумент и результат операции «модуль». Таким образом, для

использования данной подпрограммы в программе требуется только ее точная копия (в случае

EDSAC – отрезок перфоленты, подготовленный с помощью «tape duplicator»).

Перепишем для Initial Orders 2 приведенный выше фрагмент программы, реализующий

вычисление суммы модулей двух чисел:

GK ; сохранение адреса текущей целевой ячейки (72)

T 45 K ;}

45: P 305 F ;} H = <Arg> = 305

46: P 0 F ; N = <Res> = 0

TZ ; восстановление адреса текущей целевой ячейки (72)

GK ;\

72: TN ; }

73: AH ; }

74: E5 ; } точная копия подпрограммы

75: TN ; } (Mem[0] = |Mem[305]|)

76: SH ; }

77: TN ;/

GK ; сохранение адреса текущей целевой ячейки (78)

T 45 K ;}

45: P 300 F ;} H = <Arg> = 300

Page 5: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

46: P 305 D ; N = <Res> = 305

TZ ; восстановление адреса текущей целевой ячейки (78)

GK ;\

78: TN ; }

79: AH ; }

80: E5 ; } точная копия подпрограммы

81: TN ; } (Mem[305] = |Mem[300]|)

82: SH ; }

83: TN ;/

84: A 305 S ; Acc = Acc + Mem[305] (= Mem[305])

85: A 0 S ; Acc = Acc + Mem[0]

86: T 305 S ; Mem[300] = Acc

Соглашения об использовании аккумулятора В EDSAC, как в одноадресной машине, центральную роль в вычислениях играет аккумулятор.

Типичная последовательность инструкций, решающая некоторую локальную задачу,

обеспечивает формирование результата в аккумуляторе и либо принятие решения (инструкции

“E” и “G”), либо запись результата в соответствующую ячейку памяти.

Также следует отметить, что в EDSAC нет инструкции «загрузки» данных в аккумулятор или

арифметических инструкций с предварительным обнулением аккумулятора (как, например, в

IAS), зато имеется инструкция записи значения аккумулятора в память с последующим

обнулением аккумулятора - инструкция “T”.

Учитывая сказанное, при работе с EDSAC удобно следовать одному из следующих

(альтернативных) соглашений об условиях входа в открытую подпрограмму:

а) аргументы подпрограммы находятся в памяти, аккумулятор обнулен;

б) аргумент подпрограммы находится в аккумуляторе.

Аналогично формулируются соглашения об условиях выхода из подпрограммы:

а) результат(ы) находятся в памяти, аккумулятор обнулен;

б) результат подпрограммы находится в аккумуляторе.

Для рассматриваемой подпрограммы имеет смысл любая комбинация соглашений о входе и

выходе, однако наиболее «естественными» являются комбинации а-а и б-б. Отметим, что

приведенная выше подпрограмма уже отвечает соглашению а) о выходе, а использование

соглашения а) о входе позволяет ее упростить (а также снять ограничение на размещение

аргумента и результата):

GK

AH

E4

TN

SH

TN

Page 6: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

В рассмотренном выше фрагменте программы старую реализацию подпрограммы вычисления

модуля можно просто заменить новой. Альтернативно, фрагмент можно существенно изменить,

упростив его для понимания (см. выше).

Упражнения

1) Разработайте подпрограмму вычисления модуля, используя комбинацию соглашений б-б.

2) Разработайте фрагмент программы для комбинаций соглашений а-а и б-б. Стремитесь

сделать его более простым для понимания.

Closed subroutines

Идея замкнутой подпрограммы Если открытая подпрограмма используется в различных местах5 (points) программы, она должна

быть представлена в нескольких копиях, как на ленте, так и в памяти машины после окончания

загрузки. Проблему неэффективного использования памяти решают замкнутые подпрограммы

(closed subroutines).

Как и прежде, подпрограмма является последовательностью инструкций, решающей

определенную задачу. Однако теперь эти инструкции представлены в памяти машины (и на ленте)

один раз.

Переход к выполнению инструкций подпрограммы – вход в подпрограмму (entering the

subroutine)6 - осуществляется инструкцией передачи управления (“E” или “G”), напротив, в случае

открытой подпрограммы «вход» в нее осуществлялся в результате последовательного исполнения

команд.

Передача аргументов в замкнутую подпрограмму и возврат результатов ее работы может

осуществляться так же, как и в случае открытых подпрограмм. Следует, однако, отметить, что

поскольку инструкции подпрограммы находятся в памяти в единственном экземпляре, адреса

аргументов и результатов фиксированы для всей подпрограммы, а не для точки ее вызова (call

point). Действительно, поскольку в EDSAC, как и большинстве первых ЭВМ, адрес целевой ячейки

памяти указывается в коде инструкции (используется прямая адресация, direct addressing),

единственный способ изменить адреса аргументов и результатов состоит в согласованной

модификации адресных полей всех инструкций подпрограммы, обращающихся к аргументам или

записывающих результаты. С этой проблемой мы уже встречались раньше, и решили ее с

помощью механизма параметров Initial Orders 2. Отличие состоит в том, что теперь

модифицировать инструкции пришлось бы во время исполнения (run-time) программы – в каждой

точке вызова, перед входом в подпрограмму – а не во время загрузки (load-time). Очевидно, это

связано с затратами как времени (на выполнение инструкций, осуществляющих такую

модификацию), так и памяти (на хранение этих инструкций) – чего мы как раз хотели избежать.

Более сложным является вопрос выхода из подпрограммы (leaving the subroutine)7. После

выполнения всех инструкций подпрограммы следует продолжить выполнение программы с того

5 Фраза «используется несколько раз» здесь может быть истолкована двояко, например, выполняемая в

цикле подпрограмма используется (выполняется) множество раз, хотя в коде программы присутствует только одна ее копия. 6 Другие термины: «обращение к подпрограмме», «вызов подпрограммы» (calling the subroutine).

7 Другой термин – «возврат из подпрограммы» (returning from the subroutine).

Page 7: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

места, где оно было прервано, чтобы исполнить подпрограмму. В EDSAC этого можно добиться

только одним способом – использованием инструкции перехода (“E” или “G”). Но какой адрес

должен быть указан в этой инструкции? Минутное размышление показывает, что этот адрес,

вообще говоря, изменяется от вызова к вызову.

Снова рассмотрим эти вопросы на примере фрагмента программы, в котором реализуется

вычисление суммы модулей чисел в ячейках 300 и 305 с записью результата в ячейку 305. Для

решения задачи используется подпрограмма, вычисляющая модуль числа в ячейке 200 с записью

результата в ячейку 201. Инструкции подпрограммы размещаются с адреса 50, а инструкции

рассматриваемого фрагмента программы – с адреса 72.

T 50 K ; целевой адрес = 50

GK ; = 50

+0: T 201 F ;\

+1: A 200 F ; }

+2: E 5 ; } Mem[201] = |Mem[200]|, Acc = 0

+3: T 201 F ; }

+4: S 200 F ; }

+5: T 201 F ;/

+6: E ??? F ; возврат из подпрограммы

T 72 K ; целевой адрес = 72

GK ; = 72

72: T 200 F ;\

73: A 300 F ; } Mem[200] = Mem[300], Acc = 0

74: T 200 F ;/

75: E 50 F ; вызов подпрограммы

76: T 200 F ;\

77: A 305 F ; } Mem[200] = Mem[305]

78: T 200 F ;/

79: A 201 F ;\ Mem[305] = Mem[201] (= |Mem[300]|), Acc = 0

80: T 305 F ;/

81: E 50 F ; вызов подпрограммы

82: T 200 F ;\

83: A 201 F ; }

84: A 305 F ; } Mem[305] = Mem[201] + Mem[305], Acc = 0

85: T 305 F ;/

Следует пояснить, что в приведенном фрагменте программы не делается каких-либо

предположений о содержимом аккумулятора после выхода из подпрограммы, в связи с этим

«загрузка» значения из памяти в аккумулятор осуществляется парой инструкций “T” и “A”, ячейка

200 при этом используется в качестве рабочей (в инструкции “T”). В остальном программа должна

быть понятна.

Передача управления подпрограмме выполняется дважды: инструкцией 75 и инструкцией 81.

После входа в подпрограмму в результате выполнения инструкции 75 ожидается, что будут

Page 8: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

выполнены инструкции подпрограммы, после чего исполнение возобновиться с инструкции 76. В

случае же входа в подпрограмму в результате выполнения инструкции 81, исполнение должно

продолжиться с инструкции 82. Как можно видеть, этого можно добиться только согласованным

изменением адресного поля последней инструкции “E” подпрограммы.

Информация о том, куда следует передать управление после завершения подпрограммы, имеется

только в точке вызова. Тогда очевидным решением проблемы возврата является формирование и

запись инструкции возврата в точке вызова перед обращением к подпрограмме. Эту задачу

можно было бы решить, например, так:

75: T 56 F ;\

76: A 79 F ; }

77: T 56 F ; } вместо “75: E 50 F”

78: E 50 F ; }

79: E 80 F ;/

80: …

Обращение к подпрограмме вместо 1 инструкции “E 50 F” состоит теперь из 5 инструкций (ячейки

75-78) и одной константы (ячейка 79). Таким образом, после выполнения подпрограммы

управление должно быть передано на адрес 80. Для этого в конце подпрограммы должна быть

выполнена инструкция “E 80 F”8, и код именно этой инструкции находится в ячейке 79. Вызов

подпрограммы осуществляется следующим образом:

а) инструкция 75 обнуляет аккумулятор, при этом в качестве рабочей используется ячейка 56,

в которую позже (шаг в) будет записана инструкция возврата из подпрограммы;

б) инструкция 76 загружает в аккумулятор заранее сформированный (на этапе загрузки) код

инструкции возврата из подпрограммы, соответствующий данной точке вызова;

в) инструкция 77 записывает этот код в ячейку 56 – последнюю ячейку, отведенную для

инструкций подпрограммы;

г) инструкция 78 передает управление подпрограмме (условие перехода инструкции “E”

выполнено, поскольку аккумулятор содержит 0).

Далее будут исполнены инструкции в ячейках 50-55, и управление дойдет до инструкции 56,

которая в этот момент будет гласить “E 80 F”, она будет исполнена и в результате управление

будет передано на инструкцию 80, как и требовалось. Еще раз подчеркнем, что в ячейке 79

находится, по смыслу, не инструкция, которую следует исполнить, а константа (число), которая

является кодом инструкции.

Следует также отметить, что приведенное решение подразумевает, что при завершении

подпрограммы в аккумуляторе находится неотрицательное число, иначе инструкция возврата “E”

будет, по сути, просто проигнорирована. В данном конкретном примере это требование

обеспечивается, поскольку инструкция 56 исполняется либо после инструкции 52 при

неотрицательном значении аккумулятора, либо после инструкции 55 при нулевом значении

аккумулятора. В общем случае выполнение этого требования может быть обеспечено только

установлением соглашения о неотрицательном значении аккумулятора на выходе

подпрограммы, которому должны следовать все разработчики подпрограмм.

8 Предполагая, что в аккумуляторе при этом находится неотрицательное число, так что условие перехода

инструкции “E” выполнено.

Page 9: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

Недостатком приведенной схемы вызова подпрограммы, конечно, являются высокие накладные

расходы – для вызова подпрограммы требуется выполнение 4 инструкций и 5 ячеек памяти. Еще

одним существенным недостатком является необходимость знать положение инструкции

возврата (т.е. длину подпрограммы) в каждом месте обращения к ней.

Упражнения

1) Измените предложенную схему вызова (и строения подпрограммы) так, чтобы в точке

вызова не требовалось знать длину подпрограммы.

2) Разработайте параметризованную открытую подпрограмму, которая реализует передачу

управления подпрограмме. Какие соглашения об использовании аккумулятора удобно

использовать в данном случае?

Организация вызова и возврата из подпрограмм в EDSAC Главным недостатком предложенной выше схемы вызова подпрограммы является ее

неэффективность. Источником этой неэффективности является дублирование кода (code

duplication)9 обеспечивающего формирование инструкции возврата из подпрограммы, в каждой

точке вызова. Решением, следовательно, является перенос этих инструкций в саму подпрограмму.

Однако, как отмечено выше, информация об адресе возврата имеется только в точке вызова

подпрограммы. Следовательно, эта информация должна передаваться в подпрограмму из точки

вызова (как передаются другие аргументы).

Обозначив основную идею, сразу перейдем к описанию схемы вызова замкнутой подпрограммы,

которая применялась в EDSAC. В точке вызова использовалась следующая пара инструкций:

; здесь Acc = 0

N+0: A <N> F ; Acc = код инструкции “A <N> F”

N+1: G <M> F ; переход к подпрограмме, размещенной с адреса M

N+2: …

Обращение к подпрограмме состоит из 2 инструкций (ячейки N и N+1), следовательно, адресом

возврата является (N+2).

Формирование инструкции возврата обеспечивала следующая пара инструкций в начале

подпрограммы:

; здесь Acc = код инструкции “A <N> F”

M+0: A 3 F ; Acc += Mem[3] – формирование инструкции возврата

M+1: T <M+r> F ; запись инструкции возврата

… ; } инструкции подпрограммы, на выходе Acc >= 0

M+r: E 0 F ; <- сюда записывается инструкция возврата

Инструкция (N+0) обеспечивает загрузку в аккумулятор кода самой инструкции. В результате ее

выполнения содержимое аккумулятора имеет следующий вид:

9 «Дублирование кода» – устоявшийся термин, в общем случае, обозначающий многократное

использовании кода (с незначительными изменениями).

Page 10: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

Можно видеть, что старший (знаковый) разряд аккумулятора установлен, так что условие

перехода инструкции “G” (в ячейке N+1) выполнено.

Инструкция возврата должна иметь вид

⟨ ⟩ ⏟

Легко видеть, что для формирования этого кода в аккумуляторе (с учетом переполнения

разрядной сетки) к его содержимому следует прибавить константу:

Эта константа (соответствующая псевдоинструкции “U 2 F”) одинакова для всех подпрограмм,

следовательно, может находиться в памяти в единственном экземпляре. Константа заносится в

ячейку 3 загрузчиком Initial Orders 2. Эта ячейка резервируется (reserved) т.е. не должна

изменяться в ходе выполнения программы.

Учитывая сказанное, легко понять, что инструкция M обеспечивает формирование в старших

разрядах аккумулятора кода инструкции возврата, а инструкция (M+1) записывает

сформированный код в соответствующую ячейку памяти (с адресом M+r). При разработке

подпрограммы следует обеспечить формирование в аккумуляторе неотрицательного значения,

чтобы выполнялось условие перехода инструкции “E”.

Рассмотренную выше подпрограмму и фрагмент программы, ее использующей, следует

переписать следующим образом:

T 50 K ; целевой адрес = 50

GK ; = 50

+0: A 3 F ; формирование кода инструкции возврата в Acc

+1: T 7 ; запись сформированной инструкции возврата, Acc = 0

+2: A 200 F ;\

+3: E 6 ; }

+4: T 201 F ; } Mem[201] = |Mem[200]|, Acc = 0

+5: S 200 F ; }

+6: T 201 F ;/

+7: E 0 F ; <- сюда записывается инструкция возврата

T 72 K ; целевой адрес = 72

GK ; = 72

+0: T 200 F ;\

+1: A 300 F ; } Mem[200] = Mem[300], Acc = 0

Page 11: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

+2: T 200 F ;/

+3: A 3 ;\ вызов подпрограммы

+4: G 50 F ;/

+5: T 200 F ;\

+6: A 305 F ; } Mem[200] = Mem[305]

+7: T 200 F ;/

+7: A 201 F ;\ Mem[305] = Mem[201] (= |Mem[300]|), Acc = 0

+8: T 305 F ;/

+9: A 9 ;\ вызов подпрограммы

+10: G 50 F ;/

+11: T 200 F ;\

+12: A 201 F ; }

+13: A 305 F ; } Mem[305] = Mem[201] + Mem[305], Acc = 0

+14: T 305 F ;/

Поясним, что в первой точке вызова подпрограммы при считывании с ленты последовательности

“A 3 ” загрузчик Initial Orders 2 сформирует в ячейке 75 (+3=72+3=75) команду “A 75 F”.

Аналогично, во второй точке вызова символы “A 9 ” на ленте соответствуют команде “A 81 F” в

ячейке 81 (+9=72+9=81).

Упражнения

1) Оцените количественно экономию процессорного времени и памяти, которая

обеспечивается стандартной схемой вызова замкнутых подпрограмм по сравнению с

предложенной ранее схемой.

2) Обычно в EDSAC предполагалось, что на выходе замкнутой подпрограммы аккумулятор

обнулен. Насколько естественно это соглашение? Оптимизируйте приведенный фрагмент

программы с учетом этого соглашения.

3) Некоторые замкнутые подпрограммы EDSAC формировали результат в аккумуляторе. На

что должны обращать внимание разработчики таких подпрограмм?

4) Разработайте подпрограмму вычисления модуля с настраиваемыми адресами аргумента и

результата (механизм параметров Initial Orders 2). Добавьте необходимые директивы

Initial Orders 2 в приведенный выше пример.

5) Разработайте подпрограмму вычисления модуля с настраиваемым адресом аргумента

(механизм параметров Initial Orders 2), формирующую результат в аккумуляторе (с учетом

п.3).

Параметризация подпрограмм До настоящего момента предполагалось, что адреса аргументов (и результатов) подпрограммы

фиксируются в момент ее разработки. Так, в приведенном в настоящем разделе примере адреса

200 и 201 «зашиты» в текст подпрограммы, что, разумеется, усложняет ее применение в

программе (и ограничивает возможности ее повторного использования)10. Несмотря на этот

недостаток, в библиотеке EDSAC этот подход использовался весьма широко с тем, разумеется,

10

Заметим, что в результате этого адреса 200 и 201 «зашиты» также в программу, обращающуюся к данной подпрограмме. В случае, если подпрограмма изменится, и, по какой-то причине, будут использованы другие адреса аргумента и результата, необходимо скорректировать все программы, использующие данную подпрограмму.

Page 12: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

уточнением, что все подпрограммы использовали не произвольные (200, 201), а младшие адреса

(в основном, 0-1 и 4-11).

Рассмотренный ранее механизм параметров Initial Orders 2 позволяет избавиться от явного

задания используемых констант, в том числе адресов, в подпрограмме. В библиотеке

подпрограмм EDSAC с этой целью использовались параметры “H”, “N” и т.д., задаваемые таким

образом параметры подпрограммы обозначались термином “preset parameters”. Переработаем

приведенный выше пример с учетом этой идеи11:

; «Настройка» подпрограммы на конкретное применение

T 50 K ; целевой адрес = 50

G K ; = 50

T 45 K ;\

P 200 F ; } установка параметров “H” и “N”

P 201 F ;/

T Z ; восстановление адреса текущей целевой ячейки (50)

; Текст подпрограммы (не требующий каких-либо изменений)

+0: A 3 F ; формирование кода инструкции возврата в Acc

+1: T 7 ; запись сформированной инструкции возврата, Acc = 0

+2: A 0 H ;\

+3: E 6 ; }

+4: T 0 N ; } Mem[<N>] = |Mem[<H>]|, Acc = 0

+5: S 0 H ; }

+6: T 0 N ;/

+7: E 0 F ; <- сюда записывается инструкция возврата

В некоторых подпрограммах использовался также (или вместо расмотренного) другой подход к

передаче параметров, который обозначался термином “program parameters”. При этом значения

параметров подпрограммы размещались в ячейках памяти после инструкции перехода к началу

подпрограммы:

; здесь Acc = 0

N+0: A <N> F ; Acc = код инструкции “A <N> F”

N+1: G <M> F ; переход к подпрограмме, размещенной с адреса M

N+2: P 0 F ;\ в момент выполнения инструкции перехода Mem[N+1]

… ; } ячейки содержат значения параметров подпрограммы

N+k: P 0 F ;/ (обычно, определяемые во время выполнения)

Доступ к значениям этих параметров в подпрограмме осуществляется подобно тому, как

реализуется возврат из подпрограммы.

11

Следует отметить, что здесь нет ничего нового: выше мы уже переносили фиксацию используемых адресов с этапа разработки на этап загрузки (открытой) подпрограммы.

Page 13: Open subroutines - kspt.icc.spbstu.rukspt.icc.spbstu.ru/media/files/2020/lowlevelprog/edsac_sub.pdf · Идея открытой подпрограммы Приведенный ниже

Упражнения

1. Почему в библиотечных подпрограммах для передачи аргументов и возврата значений не

использовались ячейки с адресами 2 и 3?

2. Проанализируйте поведение Initial Orders 2 при загрузке программы, содержащей

псевдоинструкцию с символом blank в качестве кода инструкции.

Литература 1. D.J. Wheeler “Programme Organization and Initial Orders for the EDSAC”. Proceedings of the

Royal Society of London. Series A, Mathematical and Physical Sciences, Vol. 202, No. 1071 (Aug.

22, 1950), pp. 573-589.

2. M.V. Wilkes, D.J. Wheeler, S. Gill. "The Preparation of Programs for an Electronic Digital

Computer" 2nd ed (1957), chapter 2 “Subroutines”, Part II “Specifications of EDSAC Library

Subroutines”.

3. The EDSAC Replica Project. “Tutorial Guide to the EDSAC Simulator for Windows, Macintosh,

and Linux”.