ZXNet эхоконференция «code.zx»


тема: Передача параметров при вызове ассемблерных процедур...



от: Ivan Roshin
кому: All
дата: 08 Feb 2002
Hello, All!

═══════════════════ call_asm.1 ══════════════════

(c) Иван Рощин, Москва

Fido : 2:5020/689.53
ZXNet : 500:95/462.53
E-mail: asder_ffc@softhome.net
WWW : http://www.ivr.da.ru

Передача параметров
═══════════════════
при вызове ассемблерных процедур из программы на Бейсике
════════════════════════════════════════════════════════

("Радиомир. Ваш компьютер" 10/2001)

Введение
────────

После того, как программа на Бейсике написана, часто
выясняется, что она работает непозволительно медленно. Есть
несколько способов решения этой проблемы. Можно полностью
переписать программу на ассемблере, добившись при этом
максимального быстродействия, - но на это придется затратить
много времени и сил. Можно использовать компилятор Бейсика - это
не потребует сколько-нибудь существенных усилий, но программа
не будет столь же быстрой, как написанная на ассемблере, к тому
же могут возникнуть проблемы с недостатком памяти при компиляции
или при попытке запуска откомпилированной программы. Можно,
наконец, переписать на ассемблере только те участки программы,
на выполнение которых тратится большая часть времени. При этом
быстродействие будет лишь немногим ниже по сравнению с полным
переписыванием программы на ассемблере, а объем работы будет
значительно меньше. Именно этот способ оптимизации в ряде
случаев оказывается наиболее предпочтительным.
При использовании ассемблерных процедур встает вопрос
передачи параметров. В стандартном Бейсике средства для этого
весьма скромны: предусмотрено лишь, что вызываемая процедура
может вернуть в регистровой паре BC одно значение, представля-
ющее собой целое число в диапазоне 0-65535. Скажем, при
выполнении команды

....
1000 LET A = USR 40000
....

будет вызвана ассемблерная процедура, располагающаяся по адресу
40000, после чего переменной A будет присвоено значение
регистровой пары BC.
Можно выделить для передачи параметров определенную область
памяти, записывать туда перед вызовом процедуры значения
передаваемых переменных с помощью оператора POKE, а после
вызова - считывать возвращаемые значения с помощью оператора
PEEK. Пусть, скажем, требуется передать процедуре масштабирова-
ния координат, находящейся по адресу 40004, переменные X и Y
(целые, в диапазоне 0-65535) через область памяти 40000-40003 (с
адреса 40000 располагается значение X, в формате младший
байт/старший байт; с 40002 - значение Y, в таком же формате).
Процедура изменит эти значения определенным образом, оставив
результат в тех же ячейках памяти; после этого надо прочесть
возвращенные значения и присвоить их переменным X1 и Y1.

.....
1000 POKE 40000,X-INT(X/256)*256: POKE 40001,INT(X/256)
1010 POKE 40002,Y-INT(Y/256)*256: POKE 40003,INT(Y/256)
1020 RANDOMIZE USR 40004
1030 LET X1=PEEK(40000)+PEEK(40001)*256
1040 LET Y1=PEEK(40002)+PEEK(40003)*256
.....

Как видим, при таком способе передачи параметров
увеличивается объем Бейсик-программы, тратится время на передачу
значений (иногда довольно значительное - например, при передаче
массивов), приходится запоминать, по какому адресу какая
переменная находится, да и, наконец, просто может не хватить
памяти.
Более перспективным способом представляется тот, когда
ассемблерная программа непосредственно работает с переменными
Бейсика. Рассмотрим этот способ более подробно.


Переменные Бейсика
──────────────────

Рассмотрим сначала, какие бывают в Бейсике переменные, где
и в каком формате они хранятся.
Переменные Бейсик-программы находятся в области памяти,
начало которой адресуется системной переменной VARS,
расположенной по адресу #5C4B=23627. Переменные идут строго друг
за другом, а после последней из них находится байт #80 - признак
конца области переменных.
Хотя в именах переменных допускаются и прописные, и строчные
латинские буквы, а также пробелы и цифры (цифра не может быть
первым символом), в области хранения переменных имена
преобразуются к строчным буквам, а пробелы не хранятся (таким
образом, для интерпретатора переменные Size X и sizex - одно и
то же), знак "$" для символьных переменных также не хранится.
Hиже перечислены различные типы переменных и приведен формат
их хранения (данные о формате, приведенные в [1], прямо скажу,
не отличаются полнотой и точностью, поэтому многое пришлось
изучать в ходе практических экспериментов).

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 08 Feb 2002
Hello, All!

═══════════════════ call_asm.2 ══════════════════

Числовая переменная с именем из одной буквы

- имя (1 байт)
- значение (5 байт)

Числовая переменная с именем из нескольких символов

- имя (код первого символа увеличен на #40, последнего - на #80)
- значение (5 байт)

Переменная цикла for-next

- имя (1 байт), код символа увеличен на #80
- значение (5 байт)
- ограничение (5 байт)
- приращение (5 байт)
- номер строки, в которой расположен соответствующий
оператор FOR (2 байта)
- номер следующего после FOR оператора в этой строке (1 байт)

(Примечание: переменная с именем из одной буквы, определенная
вначале как обычная, впоследствии, если она используется в
качестве переменной цикла for-next, изменяет свой формат
хранения.)

Числовой массив

- имя (1 байт), код символа увеличен на #20
- длина массива в байтах (2 байта)
- количество размерностей (1 байт)
- первая размерность (2 байта)
...........................
- последняя размерность (2 байта)
- элементы (5 байт каждый), хранящиеся в следующем порядке:
сначала возрастает последний индекс, затем предпоследний и
так далее, например (1,1), (1,2), (1,3), (2,1), (2,2), ...

Символьная переменная

- имя (1 байт), код символа уменьшен на #20
- количество символов (2 байта)
- содержимое

Символьный массив

- имя (1 байт), код символа увеличен на #60
- длина массива в байтах (2 байта)
- количество размерностей (1 байт)
- первая размерность (2 байта)
...........................
- последняя размерность (2 байта)
- элементы (1 байт каждый); порядок хранения такой же, как у
числовых массивов.

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

╔══════════════════════════╤══════════════════════════════════╗
║ Диапазон значений │ Тип переменной ║
║ первого байта │ ║
╠══════════════════════════╪══════════════════════════════════╣
║ #41-#5A │ символьная ║
╟──────────────────────────┼──────────────────────────────────╢
║ #61-#7A │ число (имя - одна буква) ║
╟──────────────────────────┼──────────────────────────────────╢
║ #81-#9A │ числовой массив ║
╟──────────────────────────┼──────────────────────────────────╢
║ #A1-#BA │ число (имя - несколько символов) ║
╟──────────────────────────┼──────────────────────────────────╢
║ #C1-#DA │ символьный массив ║
╟──────────────────────────┼──────────────────────────────────╢
║ #E1-#FA │ переменная цикла ║
╚══════════════════════════╧══════════════════════════════════╝

Таблица 1

Приведу некоторые сведения о пятибайтной форме представления
чисел, используемой при хранении переменных. Целые числа в
диапазоне 0..65535 представляются следующим образом:

┌─────┬─────┬──────────────────────┬─────────────────────┬─────┐
│ 0 │ 0 │ младший байт числа │ старший байт числа │ 0 │
│ │ │ │ │ │
└─────┴─────┴──────────────────────┴─────────────────────┴─────┘

А целые числа в диапазоне -65535..-1 - так:

┌─────┬─────┬──────────────────────┬─────────────────────┬─────┐
│ 0 │ #FF │ младший байт числа │ старший байт числа │ 0 │
│ │ │(число представляется в дополнительном коде)│ │
└─────┴─────┴──────────────────────┴─────────────────────┴─────┘

Вещественные числа хранятся в более сложной форме. Сведения
о формате их хранения вы можете почерпнуть, например, из [3].
Здесь я их не привожу, так как работа с такими числами на
ассемблере оказывается необходимой лишь в очень редких случаях.
Часто можно преобразовать решаемую задачу так, чтобы работать
только с целыми числами, что намного ускоряет вычисления.

Попробуем теперь, вооружившись полученными знаниями,
"расшифровать" содержимое реальной области переменных, дамп
которой приведен на рис. 1.

┌───────────────────────┐
│pic_1.scr │
│ │
│ │
│ │
│ │
│ │
│ │
└───────────────────────┘

Рис. 1

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 08 Feb 2002
Hello, All!

═══════════════════ call_asm.3 ══════════════════

Первый байт области переменных - #98. Он принадлежит
диапазону #81-#9A, следовательно, первая переменная - числовой
массив. Как мы помним, для числового массива код символа имени
хранится увеличенным на #20. Определим имя: #98-#20=#78, это код
символа "x". В следующих двух байтах находится длина массива
(не считая байт имени и два байта самой длины). Прочитав ее
(#23=35) и перейдя на соответствующее число байтов вперед, мы
можем сразу перейти к следующей переменной. Hо пока делать этого
не будем, а рассмотрим структуру массива. В следующем байте
хранится 2 - значит, массив двумерный. По первому измерению его
размер равен 2, по второму - 3 (а всего, значит, в нем 6
элементов). Дальше располагаются сами элементы. X(1,1)=4,
X(1,2)=0, последующие элементы также равны нулю.
Первый байт следующей переменной - #C1, следовательно, это
символьный массив с именем A$... Hу и так далее; надеюсь, с
оставшейся частью этой области переменных вы сможете разобраться
самостоятельно.


Процедура определения адреса переменной
───────────────────────────────────────

Для определения местоположения переменной Бейсика удобно
воспользоваться нижеприведенной процедурой. Вот ее входные и
выходные параметры.
Вход: DE указывает на имя переменной, преобразованное в
соответствии с ее типом (т.е. имя должно быть именно в таком
виде, как оно хранится в области переменных), заканчивающееся
байтом 0.
Выход: если переменная найдена - флаг Z сброшен, HL
указывает в области переменных на первый байт после имени
найденной переменной, DE указывает на следующий байт после 0
(а значит, если хранить имена искомых переменных друг за другом,
то, вначале загрузив в DE адрес первого имени и затем
последовательно вызывая процедуру, мы будем последовательно
получать адреса этих переменных). Если переменная не найдена -
флаг Z установлен, DE остается без изменений.
Процедура при своей работе изменяет содержимое регистра A.

VARS EQU #5C4B

FIND_VAR LD HL,(VARS)

PUSH BC

FIND_M0 PUSH DE
PUSH HL

;Сравнение имени текущей переменной с именем искомой:

FIND_M1 LD A,(DE)
INC DE

AND A
JR Z,FIND_YES ;имена совпали

CP (HL)
INC HL
JR Z,FIND_M1

;Имена не совпали - пропускаем текущую переменную:

POP HL
POP DE
LD A,(HL)
INC HL

CP #80 ;Дошли до конца области переменных?
JR Z,FIND_NO ;Если да - выходим, флаг Z установлен.

CP #60
JR C,SKIP_M ;пропускаем символьную переменную

CP #80
FIND_M4 LD BC,5
JR C,SKIP_BC ;пропускаем числовую переменную
;с именем из одной буквы
CP #A0
JR C,SKIP_M ;пропускаем числовой массив

CP #C0
JR NC,FIND_M3

;Пропускаем числовую переменную с именем из нескольких символов:

FIND_M2 LD A,(HL)
INC HL
RLA
JR NC,FIND_M2
JR FIND_M4

FIND_M3 CP #E0
LD C,18 ;B=0!
JR NC,SKIP_BC ;пропускаем переменную цикла

;Пропуск символьной переменной или массива:

SKIP_M LD C,(HL)
INC HL
LD B,(HL)
INC HL

SKIP_BC ADD HL,BC
JR FIND_M0

FIND_YES POP BC ;снимаем два теперь не нужных числа со стека
POP BC
INC A ;A станет равным 1, флаг Z сбросится

FIND_NO POP BC
RET ;выход

Интерпретатор Бейсика производит подобный поиск при каждом
обращении к какой-либо переменной - вот вам и одна из причин
низкой скорости работы Бейсик-программ.

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 08 Feb 2002
Hello, All!

═══════════════════ call_asm.4 ══════════════════

Пример оптимизации
──────────────────

Попробуем оптимизировать по быстродействию какую-либо
простую Бейсик-программу - например, приведенную ниже.

10 LET N=0: INPUT A$
20 IF A$="" THEN GO TO 60
30 FOR I=1 TO LEN (A$)
40 IF A$(I)="X" THEN LET N=N+1
50 NEXT I
60 PRINT N

Эта программа запрашивает у пользователя строку и
подсчитывает, сколько раз в ней встретится символ "X". Критичным
по быстродействию участком, очевидно, является цикл в строках
30-50. Перепишем его на ассемблере. Пусть он размещается,
скажем, с адреса 40000.

ORG 40000

LD DE,NAME_A ;ищем переменную A$
CALL FIND_VAR

;Теперь HL указывает на 2-байтное значение, определяющее
;длину строки A$.

LD E,(HL)
INC HL
LD D,(HL)
INC HL

;DE - длина строки, HL указывает на первый символ строки.

LD BC,0 ;счетчик символов "X"

M1 LD A,(HL)
CP "X"
JR NZ,M2
INC BC

M2 INC HL
DEC DE
LD A,D
OR E
JR NZ,M1

;BC - количество символов "X" в строке.

LD DE,NAME_N ;ищем переменную N
CALL FIND_VAR

;Теперь HL указывает на значение переменной N. Помещаем туда
;содержимое BC, представленное в 5-байтной форме:

LD (HL),0
INC HL
LD (HL),0
INC HL
LD (HL),C
INC HL
LD (HL),B
INC HL
LD (HL),0

RET ;выход

NAME_A DB "a"-#20 ;имя переменной A$
DB 0
NAME_N DB "n" ;имя переменной N
DB 0

<далее следует текст процедуры FIND_VAR>

Результат компиляции запишем в файл "file1". После этого
остается лишь переписать исходную программу:

5 CLEAR 39999: RANDOMIZE USR 15619: REM: LOAD "file1" CODE
10 LET N=0: INPUT A$
20 IF A$="" THEN GO TO 60
30 RANDOMIZE USR 40000
60 PRINT N

Hапомню условия, которым должна удовлетворять вызываемая из
Бейсика ассемблерная процедура.

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

- значение регистровой пары HL' при выходе из процедуры должно
быть таким же, как и при входе, - равным #2758. Так что, если
в процедуре вы использовали HL' для каких-то своих целей,
перед выходом не забудьте поставить команды LD HL,#2758: EXX.

- значение регистровой пары IY не должно меняться при
включенных прерываниях, так как оно используется системной
процедурой обработки прерываний. Поэтому, если вам нужно
использовать IY, вначале запретите прерывания, а перед их
разрешением не забудьте восстановить содержимое IY (там
должно быть число #5C3A).

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 08 Feb 2002
Hello, All!

═══════════════════ call_asm.5 ══════════════════

Hесколько полезных советов
──────────────────────────

Значения числовых переменных лучше преобразовать для
последующей обработки из 5-байтной формы в одно- или
двухбайтную, в зависимости от диапазона: так их можно будет
быстрее обрабатывать. Если нужно работать с массивами, и в
памяти достаточно свободного места, стоит скопировать их на
адреса, кратные 256: это позволит ускорить доступ к элементам
такого массива (подробнее о том, как быстро работать с
массивами, вы можете прочитать в [4]).


Создание новых переменных
─────────────────────────

Может быть так, что в переписываемом фрагменте программы
выполняется присвоение значения ранее еще не использовавшейся
переменной. В этом случае информация о ней отсутствует в области
переменных, и, следовательно, надо ее туда добавить. Для этого
необходимо выделить в конце области переменных (между концом
последней переменной и байтом #80) область памяти необходимой
длины и занести в нее имя и значение новой переменной.
Выделение области памяти выполняется с помощью процедуры
MAKE_ROOM, расположенной в ПЗУ по адресу #1655. При ее вызове
в регистровой паре HL должен находиться адрес, с которого
нужно выделить место, а в BC - длина выделяемой области.
Адрес выделяемой области памяти нетрудно определить, если
учесть, что непосредственно за областью переменных располагается
область редактируемых строк программы, адрес начала которой
хранится в системной переменной E_LINE. Следовательно, для
получения искомого адреса достаточно взять значение E_LINE и
уменьшить его на 1.
Рассмотрим пример. Пусть имеется такая программа:

10 INPUT A: INPUT B
20 LET C=A+B
30 PRINT C

и нам нужно переписать на ассемблере строку 20. Известно, что
переменные A, B и C - целые, и их значения принадлежат диапазону
0..65535. В этом случае ассемблерный текст будет таким:

ORG 40000

MAKE_ROOM EQU #1655
E_LINE EQU 23641

LD DE,NAME_A
CALL FIND_VAR

;Теперь HL указывает на значение переменной A,
;DE указывает на NAME_B.

INC HL
INC HL
LD C,(HL)
INC HL
LD B,(HL)

;BC - значение переменной A.

CALL FIND_VAR

INC HL
INC HL
LD E,(HL)
INC HL
LD D,(HL)

;DE - значение переменной B.

EX DE,HL
ADD HL,BC ;вычислили C
PUSH HL ;запомнили в стеке

;Выделяем место для переменной C.

LD HL,(E_LINE)
DEC HL ;где выделяем
PUSH HL
LD BC,6 ;сколько выделяем
CALL MAKE_ROOM
POP HL

LD (HL),"c" ;имя переменной
INC HL

POP DE ;сняли со стека
LD (HL),0 ;значение переменной
INC HL ;и записываем его
LD (HL),0 ;в 5-байтной
INC HL ;форме
LD (HL),E
INC HL
LD (HL),D
INC HL
LD (HL),0

RET ;выход

NAME_A DB "a" ;имя переменной A
DB 0
NAME_B DB "b" ;имя переменной B
DB 0

<далее следует текст процедуры FIND_VAR>


Литература
──────────

1. "Бейсик ZX Spectrum". Москва, VA PRINT, 1993.
2. "Секреты ПЗУ". "ZX-Ревю" 10/1991.
3. "Персональный компьютер ZX Spectrum. Программирование
в машинных кодах и на языке ассемблера". Москва, "Инфорком",
1993.
4. И.Рощин. "Оптимизация на примере intro "Start". "Радиомир.
Ваш компьютер" 7-10/2001.

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 08 Feb 2002
Hello, All!

═══════════════════ call_asm.1 ══════════════════

(c) Иван Рощин, Москва

Fido : 2:5020/689.53
ZXNet : 500:95/462.53
E-mail: asder_ffc@softhome.net
WWW : http://www.ivr.da.ru

Передача параметров
═══════════════════
при вызове ассемблерных процедур из программы на Бейсике
════════════════════════════════════════════════════════

("Радиомир. Ваш компьютер" 10/2001)

Введение
────────

После того, как программа на Бейсике написана, часто
выясняется, что она работает непозволительно медленно. Есть
несколько способов решения этой проблемы. Можно полностью
переписать программу на ассемблере, добившись при этом
максимального быстродействия, - но на это придется затратить
много времени и сил. Можно использовать компилятор Бейсика - это
не потребует сколько-нибудь существенных усилий, но программа
не будет столь же быстрой, как написанная на ассемблере, к тому
же могут возникнуть проблемы с недостатком памяти при компиляции
или при попытке запуска откомпилированной программы. Можно,
наконец, переписать на ассемблере только те участки программы,
на выполнение которых тратится большая часть времени. При этом
быстродействие будет лишь немногим ниже по сравнению с полным
переписыванием программы на ассемблере, а объем работы будет
значительно меньше. Именно этот способ оптимизации в ряде
случаев оказывается наиболее предпочтительным.
При использовании ассемблерных процедур встает вопрос
передачи параметров. В стандартном Бейсике средства для этого
весьма скромны: предусмотрено лишь, что вызываемая процедура
может вернуть в регистровой паре BC одно значение, представля-
ющее собой целое число в диапазоне 0-65535. Скажем, при
выполнении команды

....
1000 LET A = USR 40000
....

будет вызвана ассемблерная процедура, располагающаяся по адресу
40000, после чего переменной A будет присвоено значение
регистровой пары BC.
Можно выделить для передачи параметров определенную область
памяти, записывать туда перед вызовом процедуры значения
передаваемых переменных с помощью оператора POKE, а после
вызова - считывать возвращаемые значения с помощью оператора
PEEK. Пусть, скажем, требуется передать процедуре масштабирова-
ния координат, находящейся по адресу 40004, переменные X и Y
(целые, в диапазоне 0-65535) через область памяти 40000-40003 (с
адреса 40000 располагается значение X, в формате младший
байт/старший байт; с 40002 - значение Y, в таком же формате).
Процедура изменит эти значения определенным образом, оставив
результат в тех же ячейках памяти; после этого надо прочесть
возвращенные значения и присвоить их переменным X1 и Y1.

.....
1000 POKE 40000,X-INT(X/256)*256: POKE 40001,INT(X/256)
1010 POKE 40002,Y-INT(Y/256)*256: POKE 40003,INT(Y/256)
1020 RANDOMIZE USR 40004
1030 LET X1=PEEK(40000)+PEEK(40001)*256
1040 LET Y1=PEEK(40002)+PEEK(40003)*256
.....

Как видим, при таком способе передачи параметров
увеличивается объем Бейсик-программы, тратится время на передачу
значений (иногда довольно значительное - например, при передаче
массивов), приходится запоминать, по какому адресу какая
переменная находится, да и, наконец, просто может не хватить
памяти.
Более перспективным способом представляется тот, когда
ассемблерная программа непосредственно работает с переменными
Бейсика. Рассмотрим этот способ более подробно.


Переменные Бейсика
──────────────────

Рассмотрим сначала, какие бывают в Бейсике переменные, где
и в каком формате они хранятся.
Переменные Бейсик-программы находятся в области памяти,
начало которой адресуется системной переменной VARS,
расположенной по адресу #5C4B=23627. Переменные идут строго друг
за другом, а после последней из них находится байт #80 - признак
конца области переменных.
Хотя в именах переменных допускаются и прописные, и строчные
латинские буквы, а также пробелы и цифры (цифра не может быть
первым символом), в области хранения переменных имена
преобразуются к строчным буквам, а пробелы не хранятся (таким
образом, для интерпретатора переменные Size X и sizex - одно и
то же), знак "$" для символьных переменных также не хранится.
Ниже перечислены различные типы переменных и приведен формат
их хранения (данные о формате, приведенные в [1], прямо скажу,
не отличаются полнотой и точностью, поэтому многое пришлось
изучать в ходе практических экспериментов).

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 08 Feb 2002
Hello, All!

═══════════════════ call_asm.3 ══════════════════

Первый байт области переменных - #98. Он принадлежит
диапазону #81-#9A, следовательно, первая переменная - числовой
массив. Как мы помним, для числового массива код символа имени
хранится увеличенным на #20. Определим имя: #98-#20=#78, это код
символа "x". В следующих двух байтах находится длина массива
(не считая байт имени и два байта самой длины). Прочитав ее
(#23=35) и перейдя на соответствующее число байтов вперед, мы
можем сразу перейти к следующей переменной. Но пока делать этого
не будем, а рассмотрим структуру массива. В следующем байте
хранится 2 - значит, массив двумерный. По первому измерению его
размер равен 2, по второму - 3 (а всего, значит, в нем 6
элементов). Дальше располагаются сами элементы. X(1,1)=4,
X(1,2)=0, последующие элементы также равны нулю.
Первый байт следующей переменной - #C1, следовательно, это
символьный массив с именем A$... Ну и так далее; надеюсь, с
оставшейся частью этой области переменных вы сможете разобраться
самостоятельно.


Процедура определения адреса переменной
───────────────────────────────────────

Для определения местоположения переменной Бейсика удобно
воспользоваться нижеприведенной процедурой. Вот ее входные и
выходные параметры.
Вход: DE указывает на имя переменной, преобразованное в
соответствии с ее типом (т.е. имя должно быть именно в таком
виде, как оно хранится в области переменных), заканчивающееся
байтом 0.
Выход: если переменная найдена - флаг Z сброшен, HL
указывает в области переменных на первый байт после имени
найденной переменной, DE указывает на следующий байт после 0
(а значит, если хранить имена искомых переменных друг за другом,
то, вначале загрузив в DE адрес первого имени и затем
последовательно вызывая процедуру, мы будем последовательно
получать адреса этих переменных). Если переменная не найдена -
флаг Z установлен, DE остается без изменений.
Процедура при своей работе изменяет содержимое регистра A.

VARS EQU #5C4B

FIND_VAR LD HL,(VARS)

PUSH BC

FIND_M0 PUSH DE
PUSH HL

;Сравнение имени текущей переменной с именем искомой:

FIND_M1 LD A,(DE)
INC DE

AND A
JR Z,FIND_YES ;имена совпали

CP (HL)
INC HL
JR Z,FIND_M1

;Имена не совпали - пропускаем текущую переменную:

POP HL
POP DE
LD A,(HL)
INC HL

CP #80 ;Дошли до конца области переменных?
JR Z,FIND_NO ;Если да - выходим, флаг Z установлен.

CP #60
JR C,SKIP_M ;пропускаем символьную переменную

CP #80
FIND_M4 LD BC,5
JR C,SKIP_BC ;пропускаем числовую переменную
;с именем из одной буквы
CP #A0
JR C,SKIP_M ;пропускаем числовой массив

CP #C0
JR NC,FIND_M3

;Пропускаем числовую переменную с именем из нескольких символов:

FIND_M2 LD A,(HL)
INC HL
RLA
JR NC,FIND_M2
JR FIND_M4

FIND_M3 CP #E0
LD C,18 ;B=0!
JR NC,SKIP_BC ;пропускаем переменную цикла

;Пропуск символьной переменной или массива:

SKIP_M LD C,(HL)
INC HL
LD B,(HL)
INC HL

SKIP_BC ADD HL,BC
JR FIND_M0

FIND_YES POP BC ;снимаем два теперь не нужных числа со стека
POP BC
INC A ;A станет равным 1, флаг Z сбросится

FIND_NO POP BC
RET ;выход

Интерпретатор Бейсика производит подобный поиск при каждом
обращении к какой-либо переменной - вот вам и одна из причин
низкой скорости работы Бейсик-программ.

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 08 Feb 2002
Hello, All!

═══════════════════ call_asm.4 ══════════════════

Пример оптимизации
──────────────────

Попробуем оптимизировать по быстродействию какую-либо
простую Бейсик-программу - например, приведенную ниже.

10 LET N=0: INPUT A$
20 IF A$="" THEN GO TO 60
30 FOR I=1 TO LEN (A$)
40 IF A$(I)="X" THEN LET N=N+1
50 NEXT I
60 PRINT N

Эта программа запрашивает у пользователя строку и
подсчитывает, сколько раз в ней встретится символ "X". Критичным
по быстродействию участком, очевидно, является цикл в строках
30-50. Перепишем его на ассемблере. Пусть он размещается,
скажем, с адреса 40000.

ORG 40000

LD DE,NAME_A ;ищем переменную A$
CALL FIND_VAR

;Теперь HL указывает на 2-байтное значение, определяющее
;длину строки A$.

LD E,(HL)
INC HL
LD D,(HL)
INC HL

;DE - длина строки, HL указывает на первый символ строки.

LD BC,0 ;счетчик символов "X"

M1 LD A,(HL)
CP "X"
JR NZ,M2
INC BC

M2 INC HL
DEC DE
LD A,D
OR E
JR NZ,M1

;BC - количество символов "X" в строке.

LD DE,NAME_N ;ищем переменную N
CALL FIND_VAR

;Теперь HL указывает на значение переменной N. Помещаем туда
;содержимое BC, представленное в 5-байтной форме:

LD (HL),0
INC HL
LD (HL),0
INC HL
LD (HL),C
INC HL
LD (HL),B
INC HL
LD (HL),0

RET ;выход

NAME_A DB "a"-#20 ;имя переменной A$
DB 0
NAME_N DB "n" ;имя переменной N
DB 0

<далее следует текст процедуры FIND_VAR>

Результат компиляции запишем в файл "file1". После этого
остается лишь переписать исходную программу:

5 CLEAR 39999: RANDOMIZE USR 15619: REM: LOAD "file1" CODE
10 LET N=0: INPUT A$
20 IF A$="" THEN GO TO 60
30 RANDOMIZE USR 40000
60 PRINT N

Напомню условия, которым должна удовлетворять вызываемая из
Бейсика ассемблерная процедура.

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

- значение регистровой пары HL' при выходе из процедуры должно
быть таким же, как и при входе, - равным #2758. Так что, если
в процедуре вы использовали HL' для каких-то своих целей,
перед выходом не забудьте поставить команды LD HL,#2758: EXX.

- значение регистровой пары IY не должно меняться при
включенных прерываниях, так как оно используется системной
процедурой обработки прерываний. Поэтому, если вам нужно
использовать IY, вначале запретите прерывания, а перед их
разрешением не забудьте восстановить содержимое IY (там
должно быть число #5C3A).

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 08 Feb 2002
Hello, All!

═══════════════════ call_asm.5 ══════════════════

Несколько полезных советов
──────────────────────────

Значения числовых переменных лучше преобразовать для
последующей обработки из 5-байтной формы в одно- или
двухбайтную, в зависимости от диапазона: так их можно будет
быстрее обрабатывать. Если нужно работать с массивами, и в
памяти достаточно свободного места, стоит скопировать их на
адреса, кратные 256: это позволит ускорить доступ к элементам
такого массива (подробнее о том, как быстро работать с
массивами, вы можете прочитать в [4]).


Создание новых переменных
─────────────────────────

Может быть так, что в переписываемом фрагменте программы
выполняется присвоение значения ранее еще не использовавшейся
переменной. В этом случае информация о ней отсутствует в области
переменных, и, следовательно, надо ее туда добавить. Для этого
необходимо выделить в конце области переменных (между концом
последней переменной и байтом #80) область памяти необходимой
длины и занести в нее имя и значение новой переменной.
Выделение области памяти выполняется с помощью процедуры
MAKE_ROOM, расположенной в ПЗУ по адресу #1655. При ее вызове
в регистровой паре HL должен находиться адрес, с которого
нужно выделить место, а в BC - длина выделяемой области.
Адрес выделяемой области памяти нетрудно определить, если
учесть, что непосредственно за областью переменных располагается
область редактируемых строк программы, адрес начала которой
хранится в системной переменной E_LINE. Следовательно, для
получения искомого адреса достаточно взять значение E_LINE и
уменьшить его на 1.
Рассмотрим пример. Пусть имеется такая программа:

10 INPUT A: INPUT B
20 LET C=A+B
30 PRINT C

и нам нужно переписать на ассемблере строку 20. Известно, что
переменные A, B и C - целые, и их значения принадлежат диапазону
0..65535. В этом случае ассемблерный текст будет таким:

ORG 40000

MAKE_ROOM EQU #1655
E_LINE EQU 23641

LD DE,NAME_A
CALL FIND_VAR

;Теперь HL указывает на значение переменной A,
;DE указывает на NAME_B.

INC HL
INC HL
LD C,(HL)
INC HL
LD B,(HL)

;BC - значение переменной A.

CALL FIND_VAR

INC HL
INC HL
LD E,(HL)
INC HL
LD D,(HL)

;DE - значение переменной B.

EX DE,HL
ADD HL,BC ;вычислили C
PUSH HL ;запомнили в стеке

;Выделяем место для переменной C.

LD HL,(E_LINE)
DEC HL ;где выделяем
PUSH HL
LD BC,6 ;сколько выделяем
CALL MAKE_ROOM
POP HL

LD (HL),"c" ;имя переменной
INC HL

POP DE ;сняли со стека
LD (HL),0 ;значение переменной
INC HL ;и записываем его
LD (HL),0 ;в 5-байтной
INC HL ;форме
LD (HL),E
INC HL
LD (HL),D
INC HL
LD (HL),0

RET ;выход

NAME_A DB "a" ;имя переменной A
DB 0
NAME_B DB "b" ;имя переменной B
DB 0

<далее следует текст процедуры FIND_VAR>


Литература
──────────

1. "Бейсик ZX Spectrum". Москва, VA PRINT, 1993.
2. "Секреты ПЗУ". "ZX-Ревю" 10/1991.
3. "Персональный компьютер ZX Spectrum. Программирование
в машинных кодах и на языке ассемблера". Москва, "Инфорком",
1993.
4. И.Рощин. "Оптимизация на примере intro "Start". "Радиомир.
Ваш компьютер" 7-10/2001.

════════════════════════════════════════════════

С уважением, Иван Рощин.




Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Постскриптум - О том, что будет в следующем номере.
Доработки - Схема доработки AY на SCORPION ZS.
Контактные телефоны - По всем вопросам, возникшим после прочтения номера вы можете обращаться по следующим телефонам.
Реклама - Реклама и объявления ...
Help - управление журналом.

В этот день...   24 апреля