PDA

Просмотр полной версии : try-catch на ассемблере z80



siril
23.10.2012, 15:48
Подумал я: а возникни у меня необходимость обрабатывать исключения и подниматься к процедуре catch с произвольной глубины подпрограммы, как бы я это реализовал на z80?


Вот пример на sjasmplus:


; macro
macro border color
ld a,color
out (#fe),a
endm

macro set_page pagenum
ld a,pagenum
ld bc,#7ffd
out (c),a
endm
macro stop
jp $
endm

;try-catch macro {
macro try catch_addr
ld hl, catch_addr
call begtry_subr
endm

macro epop_2
ld hl,(esp)
inc hl,hl,hl,hl
ld (esp),hl
endm

macro raise exception
ld sp,(esp) ; restore alt stack
pop de ; pop adress of main stack
pop hl ; pop adress of "catch"
ld (esp),sp ; store alt stack
ex de,hl
ld sp,hl
ld a,exception
ex de,hl
jp (hl)
endm

macro epush_hl_sp
ld (tsp),sp ; save main stack
ld sp,(esp) ; restore alt stack
push hl ; push catch_adress to alt stack
ld hl,(tsp) ;
push hl ; push main stack top to alt stack
ld (esp),sp ; save alt stack
ld sp,(tsp) ; restore main stack
endm

macro endtry
call endtry_subr
endm
;} try-catch macro


device ZXSPECTRUM128

org #6000
init
di
; устанавливаем обработчик исключений на процедуру по адресу catch0
try catch0
call setup
call start
; снимаем обработчик исключений по endtry
endtry
ld a,'A' ;ok
ret
; сюда попадём по raise с любой глубины
catch0
ld a,'B' ;plan 'B' =)
ret

;
ifused begtry_subr
begtry_subr
epush_hl_sp
ret
endif
ifused endtry_subr
endtry_subr
epop_2
ret
endif

;----------------------------------------------------------------
setup
call set_screen
ret
start
ret

set_screen
di
call cls
call scr_grid
ei
ret

cls
ld hl,#4000
ld de,#4001
ld bc,#17ff
ld a,#00
ld (hl),a
ldir
ret
scr_grid

ld hl,#5800
ld de,#5802
ld b,12
scr_grid_lp1
push bc
ld bc,#20
ld a,#0f | #40
ld (hl),a
inc hl
ld a,#0f | #00
ld (hl),a
dec hl
ldir

ld bc,#20
ld a,#0f | #00
ld (hl),a
inc hl
ld a,#0f | #40
ld (hl),a
dec hl
ldir
; вызываем исключение:
raise 1

pop bc
djnz scr_grid_lp1
ret

program_end
;{DATA ---------------------------------------------------------------
data_start

;alternative exceptions stack top:
estack_top equ #bfff
esp dw estack_top ;alt stack pointer storage
tsp dw #0000 ;main stack pointer temp storage


data_end
;DATA }

savesna "try_catch_sample.sna",#6000
display "program lenght", /a, program_end - init
display "data lenght", /a, data_end - data_start


Ну, то есть идея проста: по инструкции try запомнить текущий адрес основного стека на альтернативном стеке, там же на альтернативном стеке сохранить адрес процедуры catch.

В случае если всё ок, и мы без приключений добрались до инструкции endtry - поднимаем указатель альтернативного стека на 4 (2 байта - адрес указателя основного стека и 2 байта - адрес процедуры catch).

По инструкции raise - забираем с альтернативного стека указатель основного стека на момент инструкции try, а также адрес процедуры catch, на которую и совершаем безусловный переход, предварительно поместив в регистр A - код исключения.

Соответственно инструкции try-catch могут быть вложенными - достаточно лишь предусмотреть необходимую глубину альтернативного стека. Адрес которого задаётся двухбайтовым словом по адресу esp. (В примере это estack_top equ #bfff).

Есть ли более короткий/эффективный/разумный способ обработки исключений на асме z80? =)

И насколько оправданно пользоваться описанным выше способом? =)

Shadow Maker
23.10.2012, 15:56
Не могу придумать, зачем в Z80 использовать эксепшены таким образом. Всегда проще тупо сделать call exception или jp exception, и там установить или стек или еще чего.

siril
23.10.2012, 21:53
Не могу придумать, зачем в Z80 использовать эксепшены таким образом. Всегда проще тупо сделать call exception или jp exception, и там установить или стек или еще чего.

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

доп-стек даёт возможность вложенных try-catch'ей с проверкой в catch'е а умеем ли мы обрабатывать данный exception или нет. если нет, то raise exception этажом выше.

и даёт возможность сделать кучу raise'ов, в заранее неизвестных местах.

хотя, конечно, если мы знаем где raise exception, и если если он у нас один, то огород городить не нужно - достаточно прописать по нужным адресам значение sp и адрес catch:



;try
ld (raise_0+1),sp
ld hl,catch_0
ld (raise_0+4),hl
call something
;endtry
ld a,'A' ; plan "a"
ret
;catch
catch_0 ld a,'B' ; plan "b" =)
ret

something
;do something
;{raise exception
raise ld sp,#0000 ; will be modified by "try" - sp
jp #0000 ; will be modified by "try" - catch_0

;}raise exception


Что можно ещё придумать?

jerri
23.10.2012, 23:25
siril, а пример нормальный есть?
в виде даже С исходника

Shadow Maker
24.10.2012, 00:14
Да, мне тоже интересно, ибо я реально не могу придумать пример, где действительно ОПРАВДАНО было бы использование таких эксепшенов.

jerri
24.10.2012, 10:12
все эксцепшены на спеке обычно сводятся к



начало программы
ld (store_sp),sp
call programm
exit
ld sp,(store_sp)
ret
programm
call process
call keys
jr programm

и где нибудь дальше
если надо прекратить изза ошибок
jp exit

или пользователь нажал нужную кнопку
jp exit

Shadow Maker
24.10.2012, 10:41
Ну да, я так и сказал.

Barmaley_m
25.10.2012, 00:13
Я думаю, все можно хранить на одном и том же стеке. В макросе "try" делать следующее:
1) сохранить на стек содержимое переменных save_sp и catch_addr
2) записать в переменную save_sp новое значение стека
3) записать в переменную catch_addr новый адрес catch-блока
4) выполнять код, в котором может возникнуть исключение
5) в случае исключения:
5.1) установить адрес стека на содержимое переменной save_sp
5.2) выполнить переход на адрес catch_addr
6) необходим блок finally, который исполняется независимо от того, было ли исключение, по окончании try-блока. В блоке finally восстанавливается значение переменных save_sp и catch_addr со стека, то есть выполняются действия, обратные выполненным в п. 1).

Но для большинства реальных программ такие сложности не нужны. Я в своих программах обычно обходился одним уровнем "экстренного возврата" и, соответственно, одной переменной save_sp и фиксированным адресом блока, обрабатывающего ошибку.

Vitamin
25.10.2012, 08:32
Такие вот конструкции (try/catch/finally) обычно используются для предотвращения утечек динамически выделяемых ресурсов. На спеке такая модель не используется, отсюда и затруднения с придумыванием кейса использования.

jerri
25.10.2012, 09:08
Barmaley_m, в любом случае этот гемор нужен если памяти немерянно
а тут всего 48 кб и вся подсчитана

alone
27.10.2012, 14:39
http://lingvoforum.net/index.php?topic=47114.0

Barmaley_m
28.10.2012, 19:15
для предотвращения утечек динамически выделяемых ресурсов. На спеке такая модель не используется
Ну как же не используется? В оконных менеджерах обычно требуется динамически выделяемая память для сохранения содержимого экрана под окнами. Я еще в 1995г сделал на спеке менеджер кучи, да и уверен, кроме моей были и другие реализации. Любая ось, в том числе синклер-бейсик, выделяет динамические ресурсы. RST 8 в бейсике - простейший случай реализации исключений, хоть и одноуровневый.

Vitamin
28.10.2012, 19:26
Ну как же не используется? В оконных менеджерах обычно требуется динамически выделяемая память для сохранения содержимого экрана под окнами. Я еще в 1995г сделал на спеке менеджер кучи, да и уверен, кроме моей были и другие реализации. Любая ось, в том числе синклер-бейсик, выделяет динамические ресурсы. RST 8 в бейсике - простейший случай реализации исключений, хоть и одноуровневый.
Для сохранения экрана под окнами использовалась стековая модель обычно. Равно как и для бейсика. Назвать это кучей язык не поворачивается.

Barmaley_m
29.10.2012, 02:37
Динамически выделенные ресурсы необходимо освобождать при любой модели их организации, будь то куча, стек или еще что-то. Ключевой момент здесь в том, что имеет место динамическое выделение, а куча или не куча - дело десятое.

Vitamin
29.10.2012, 02:47
Динамически выделенные ресурсы необходимо освобождать при любой модели их организации, будь то куча, стек или еще что-то. Ключевой момент здесь в том, что имеет место динамическое выделение, а куча или не куча - дело десятое.
Ну да, если подойти с этой стороны, то вполне вписывается. Подойдем с другой стороны: используется ли на спеке "альтернативный ход выполнения программ" (сиречь исключения)? Как работает вышеупомянутый RST8?

Barmaley_m
30.10.2012, 00:20
Я думал, ты в курсе, как работает RST 8, но если нет - поясню. Он восстанавливает SP из системной переменной ERR_SP и выполняет возврат. Таким образом, при выполнении RST 8 где-нибудь во вложенной подпрограмме из бейсика, выполняется "ускоренный возврат" на верхний уровень исполнения. При этом освобождается ресурс - стек, на котором хранятся адреса вложенных подпрограмм и сохраненные значения регистров. Временно выделенная память не освобождается, но по-видимому, RST 8 не используется в тех местах бейсика, где нужно освобождать временно выделенную память, либо такое освобождение реализуется до вызова RST 8, то есть в обход механизма исключений.

Если RST 8 срабатывает при проверке синтаксиса - то бейсик отображает знак вопроса в редактируемой строке, в противном случае - печатает сообщение об ошибке. То есть налицо некое подобие блоков TRY/CATCH, так как по RST 8 возврат не всегда происходит в одно и то же место кода.

Vitamin
30.10.2012, 09:41
Я думал, ты в курсе, как работает RST 8
Откуда? Я как-то кишками бейсика никогда не интересовался:)


То есть налицо некое подобие блоков TRY/CATCH, так как по RST 8 возврат не всегда происходит в одно и то же место кода.
Тогда уж это скорее longjmp.

Barmaley_m
30.10.2012, 15:34
Откуда? Я как-то кишками бейсика никогда не интересовался:)
А зря! Бейсик написан умными людьми, я почерпнул оттуда немало идей, изучая Ян-логановский дизассемблер. Не говоря уже о том, что узнал некоторые полезные и малоизвестные особенности бейсика, которые потом использовал в своих прогах.

Тогда уж это скорее longjmp.
Который, в свою очередь, (см. Википедия Longjmp) обычно используется для реализации в языке C механизма исключений. Средства разные, концепция одна.

alone
30.10.2012, 18:15
Можно передавать ошибки насквозь через флаг CY. Но в таком стиле надо писать ВСЕ подпрограммы.

Vitamin
30.10.2012, 18:25
Который, в свою очередь, (см. Википедия Longjmp) обычно используется для реализации в языке C механизма исключений. Средства разные, концепция одна.


Можно передавать ошибки насквозь через флаг CY. Но в таком стиле надо писать ВСЕ подпрограммы.
Обращая внимание только на одну сторону исключений (альтернативный ход выполнения программы), начисто упускается другая- передача информации об этом исключении (т.е. объект исключения). В этой ситуации longjmp и прочие костыли выглядят весьма бледно.

alone
30.10.2012, 20:32
При сквозном выходе на каждом уровне созданные объекты известны. Поэтому их легко удалить, даже в строго обратном порядке.

Vitamin
30.10.2012, 20:38
При сквозном выходе на каждом уровне созданные объекты известны. Поэтому их легко удалить, даже в строго обратном порядке.
Ну так а что насчет объекта исключения?:)

Barmaley_m
30.10.2012, 21:17
Ну так а что насчет объекта исключения?:)
Ну, можно зарезервировать в памяти место и записать туда необходимую информацию об исключении. В том же бейсике после команды RST 8 следует код ошибки. Он обработчиком помещается в соответствующую системную переменную. Тем же способом можно передавать и больше информации, если необходимо.