Просмотр полной версии : SwapSet
Спецификация библиотеки SwapSet v2.0
Общее описание
Библиотека для управления изолированными контекстами регистров на Z80.
Каждый контекст представляет собой 8-байтовый блок памяти.
Структура контекста (8 байт)
Байт 0-1: AF (старший A, младший F)
Байт 2-3: BC
Байт 4-5: DE
Байт 6-7: HL
API ФУНКЦИИ
Инициализация системы
; Инициализирует систему SwapSet
; Использование: call InitSwapSet
InitSwapSet:
ld ix, RegSet0+8
ld (CurrentSet), ix
ld ix, RegSet0
jp SwapSet
Основное переключение контекста
; Переключает контекст регистров
; Вход: IX = адрес нового набора
; Использование: ld ix, NewSet \ call SwapSet
SwitchContext
SwapSet:
di
ld (StoreSP), sp
; Сохраняем текущий контекст
ld sp, (CurrentSet)
push hl
push de
push bc
push af
; Загружаем новый контекст
ld sp, ix
pop af
pop bc
pop de
pop hl
ld (CurrentSet), sp
ld sp, (StoreSP)
ei
ret
Быстрые обёртки для стандартных наборов
SwapSet0: ld ix, RegSet0 \ jr SwapSet
SwapSet1: ld ix, RegSet1 \ jr SwapSet
SwapSet2: ld ix, RegSet2 \ jr SwapSet
SwapSet3: ld ix, RegSet3 \ jr SwapSet
Геттеры (чтение из контекста)
Функции:
GetA: ld a, (ix+1) \ ret
GetBC: ld c, (ix+2) \ ld b, (ix+3) \ ret
GetDE: ld e, (ix+4) \ ld d, (ix+5) \ ret
GetHL: ld l, (ix+6) \ ld h, (ix+7) \ ret
Макросы:
MACRO Get_A: ld a, (ix+1) ENDM
MACRO Get_B: ld b, (ix+3) ENDM
MACRO Get_C: ld c, (ix+2) ENDM
MACRO Get_D: ld d, (ix+5) ENDM
MACRO Get_E: ld e, (ix+4) ENDM
MACRO Get_H: ld h, (ix+7) ENDM
MACRO Get_L: ld l, (ix+6) ENDM
MACRO Get_BC: ld b, (ix+3) \ ld c, (ix+2) ENDM
MACRO Get_DE: ld d, (ix+5) \ ld e, (ix+4) ENDM
MACRO Get_HL: ld h, (ix+7) \ ld l, (ix+6) ENDM
Сеттеры (запись в контекст)
Функции:
PutA: ld (ix+1), a \ ret
PutBC: ld (ix+2), c \ ld (ix+3), b \ ret
PutDE: ld (ix+4), e \ ld (ix+5), d \ ret
PutHL: ld (ix+6), l \ ld (ix+7), h \ ret
Макросы:
MACRO Put_A: ld (ix+1), a ENDM
MACRO Put_B: ld (ix+3), b ENDM
MACRO Put_C: ld (ix+2), c ENDM
MACRO Put_D: ld (ix+5), d ENDM
MACRO Put_E: ld (ix+4), e ENDM
MACRO Put_H: ld (ix+7), h ENDM
MACRO Put_L: ld (ix+6), l ENDM
MACRO Put_BC: ld (ix+3), b \ ld (ix+2), c ENDM
MACRO Put_DE: ld (ix+5), d \ ld (ix+4), e ENDM
MACRO Put_HL: ld (ix+7), h \ ld (ix+6), l ENDM
Управление контекстами
Сохранение текущих регистров:
SaveSet:
ld (ix+0), 0 ; F (упрощённо)
ld (ix+1), a
ld (ix+2), c
ld (ix+3), b
ld (ix+4), e
ld (ix+5), d
ld (ix+6), l
ld (ix+7), h
ret
Загрузка без переключения:
LoadSet2:
ld a, (ix+1)
ld c, (ix+2)
ld b, (ix+3)
ld e, (ix+4)
ld d, (ix+5)
ld l, (ix+6)
ld h, (ix+7)
ret
Архитектурные макросы
; Вызов функции в изолированном контексте
MACRO CALL_ISOLATED function_name
push ix
ld ix, function_name##_Set
call SwitchContext
ENDM
; Возврат из изолированной функции
MACRO RET_ISOLATED
pop ix
jp SwitchContext
ENDM
Системные переменные
StoreSP: dw 0
CurrentSet: dw RegSet0+8
Предопределённые контексты
RegSet0: ds 8
RegSet1: ds 8
RegSet2: ds 8
RegSet3: ds 8
Примеры использования
Изолированная функция:
Physics_Set: ds 8
ld ix, Physics_Set
call SwapSet
; ... код в изоляции ...
ld ix, Main_Set
call SwapSet
Работа с объектом без переключения:
ld ix, Enemy_Set
Get_A
Get_BC
Быстрое переключение:
call SwapSet0
call SwapSet1
call SwapSet2
Требования
Память: 8 байт на контекст + код библиотеки
Инициализация: обязательный вызов InitSwapSet
Прерывания: автоматически блокируются внутри SwapSet
//// ExtSwapSet.asm
// v.2.0
// 9/21/2025
//
// Needs 8 bytes per structure:
//
// RegSet dw 0 //AF
// dw 0 //BC
// dw 0 //DE
// dw 0 //HL
// End
//
// The CurrentSet variable must be initialized
// prior to first use for the mechanism to function correctly.
//
InitSwapSet
ld ix,RegSet0+8
ld (CurrentSet),ix
ld ix,RegSet0
//in ix- register set address
SwitchContext
SwapSet di
ld (StoreSP),sp
//SaveSet
ld sp,(CurrentSet) // Current Set StackEnd
push hl
push de
push bc
push af
//LoadSet
ld sp,ix //Address New Set
pop af
pop bc
pop de
pop hl
ld (CurrentSet),sp
ld sp,(StoreSP)
ei
ret
//------------------------------------------------
SwapSet0 ld ix,RegSet0
jr SwapSet
SwapSet1 ld ix,RegSet1
jr SwapSet
SwapSet2 ld ix,RegSet2
jr SwapSet
SwapSet3 ld ix,RegSet3
jr SwapSet
//------------------------------------------------
//Setters:
PutA ld (ix+1),a
ret
PutBC ld (ix+2),c
ld (ix+3),b
ret
PutDE ld (ix+4),e
ld (ix+5),d
ret
PutHL ld (ix+6),l
ld (ix+7),h
ret
//---------------------------------------
MACRO Put_A
ld (ix+1),a
ENDM
MACRO Put_B
ld (ix+3),b
ENDM
MACRO Put_C
ld (ix+2),c
ENDM
MACRO Put_D
ld (ix+5),d
ENDM
MACRO Put_E
ld (ix+4),e
ENDM
MACRO Put_H
ld (ix+7),h
ENDM
MACRO Put_L
ld (ix+6),l
ENDM
MACRO Put_BC
ld (ix+3),b
ld (ix+2),c
ENDM
MACRO Put_DE
ld (ix+5),d
ld (ix+4),e
ENDM
MACRO Put_HL
ld (ix+7),h
ld (ix+6),l
ENDM
//---------------------------------------
//Getters:
GetA ld a,(ix+1)
ret
GetBC ld c,(ix+2)
ld b,(ix+3)
ret
GetDE ld e,(ix+4)
ld d,(ix+5)
ret
GetHL ld l,(ix+6)
ld h,(ix+7)
ret
//---------------------------------------
MACRO Get_A
ld a,(ix+1)
ENDM
MACRO Get_B
ld b,(ix+3)
ENDM
MACRO Get_C
ld c,(ix+2)
ENDM
MACRO Get_D
ld d,(ix+5)
ENDM
MACRO Get_E
ld e,(ix+4)
ENDM
MACRO Get_H
ld h,(ix+7)
ENDM
MACRO Get_L
ld l,(ix+6)
ENDM
MACRO Get_BC
ld b,(ix+3)
ld c,(ix+2)
ENDM
MACRO Get_DE
ld d,(ix+5)
ld e,(ix+4)
ENDM
MACRO Get_HL
ld h,(ix+7)
ld l,(ix+6)
ENDM
//---------------------------------------
Init
SaveSet ld (ix+0),0
ld (ix+1),a
ld (ix+2),c
ld (ix+3),b
ld (ix+4),e
ld (ix+5),d
ld (ix+6),l
ld (ix+7),h
ret
//---------------------------------------
Return
LoadSet di
ld (StoreSP),sp
ld sp,ix
pop af
pop bc
pop de
pop hl
ld sp,(StoreSP)
ei
ret
Return2
LoadSet2 ld a,(ix+1)
ld c,(ix+2)
ld b,(ix+3)
ld e,(ix+4)
ld d,(ix+5)
ld l,(ix+6)
ld h,(ix+7)
ret
StoreSP dw 0
CurrentSet dw RegSet0+8
//---------------------------------------
RegSet0 dw 0 //AF
dw 0 //BC
dw 0 //DE
dw 0 //HL
RegSet1 ds 8
RegSet2 ds 8
RegSet3 ds 8
Dart Alver
07.09.2025, 12:54
Наверно стоит добавить что прерывания следует запретить, во избежание так сказать...
Ну или жестко контролировать ))
Lethargeek
07.09.2025, 13:08
хочу поделиться своей библиотекой
слишком громкое название для довольно очевидного этюда (да и то, здесь в "этюды" больше постят разные трюки)
и область применения какова? игрушечная мини-многозадачность для самых маленьких?
Наверно стоит добавить что прерывания следует запретить, во избежание так сказать...
Ну или жестко контролировать ))
ну естественно:
di
...
ei
спасибо!)
слишком громкое название для довольно очевидного этюда (да и то, здесь в "этюды" больше постят разные трюки)
и область применения какова? игрушечная мини-многозадачность для самых маленьких?
речь не о многозадачности. всего лишь структурирование кода меньше очевидных push/pop (код чище) и абстракция расширения основных наборов регистров, вызов функций использующих свой собственный контекст. общий стек для всех наборов виртуальных регистров позволяет пересылать данные между сетами.
к примеру изменили регистры в сете, переключились на новый, про первый можно забыть и не держать регистры на стеке до вызова процедуры использующей эти регистры, вызвали сет в любом месте программы по надобности.
Я не претендую на что-то новое. Но вот такая фишка пришла в голову.
Простой пример:
org 0x8000
jp main
include "conio32.asm"
include "math.asm"
include "SwapSet.asm"
fnt incbin "slim.fnt"
//---------------------------------------
main ld hl,fnt
call setfont
ld a,0x07
call setcolor
call clrscr
call InitSwapSet
call SwapSet0
ld hl,str0
call SwapSet1
ld hl,str1
call SwapSet2
ld hl,str2
call SwapSet3
ld hl,str3
ld ix,DemoSet
call SwapSet
ld hl,DemoStr
call SwapSet0
call puts
call SwapSet1
call puts
call SwapSet2
call puts
call SwapSet3
call puts
ld ix,DemoSet
call SwapSet
call puts
ret
//------------------------------------------------
DemoSet ds 8
DemoStr dz LF, LF, TAB, ATTR,04o,"AND ME!!!",LF
str0 dz ATTR,BLACK|RED, "Message from SwapSet0: Hello!",LF
str1 dz ATTR,BLACK|WHITE, "Message from SwapSet1: Hello!",LF
str2 dz ATTR,BLACK|CYAN, "Message from SwapSet2: Hello!",LF
str3 dz ATTR,BLACK|YELLOW, "Message from SwapSet3: Hello!",LF
Dart Alver
07.09.2025, 14:12
SwapSet0 ld ix,RegSet0
call SwapSet
ret
SwapSet1 ld ix,RegSet1
call SwapSet
ret
SwapSet2 ld ix,RegSet2
call SwapSet
ret
SwapSet3 ld ix,RegSet3
call SwapSet
ret
Лучше тогда уж :
SwapSet0 ld ix,RegSet0
jp SwapSet
SwapSet1 ld ix,RegSet1
jp SwapSet
SwapSet2 ld ix,RegSet2
jp SwapSet
SwapSet3 ld ix,RegSet3
jp SwapSet
Лучше тогда уж :
SwapSet0 ld ix,RegSet0
jp SwapSet
SwapSet1 ld ix,RegSet1
jp SwapSet
SwapSet2 ld ix,RegSet2
jp SwapSet
SwapSet3 ld ix,RegSet3
jp SwapSet
Ну да. Это вопрос оптимизации vs наглядность (читаемость):v2_dizzy_coder:
Это же просто обертки.
Можно обойтись без них, вообще сеты можно генерить динамически:
ld ix,NewSet
call SwapSet
NewSet ds 8
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSBRNV67bUi1An6WLHQfcakowH6wVgSX qKfdQ&s.png
Dart Alver
07.09.2025, 16:40
Если честно, тоже думаю что довольно таки редко-нужная вещь, но раз выбралось чуток ненужного времени, добавлю прожорливый по памяти и тактам вариант, который не ломает стек и соответственно не требует запрета прерываний и не занимает ix.
Минус - много места на каждый слот (програмка) и для сохранения полного регистра AF тратит больше тактов, но если сохранять только A без флагов, то даже меньше чуть (если учитывать инициацию IX).
sl_end jp .ret ; 10
.ret ret ; начальный переход на команду RET
; нужен для первого запуска
; макросы создания слотов - каждый слот - программа
MACRO reg_slotF ; AF,HL,DE,BC
call sl_end ; 17+10+103
ld hl,.sv ; 10
ld (sl_end+1),hl ; 16
1 ld hl,#0000 ; af ; 10
push hl ; 11
2 ld hl,#0000 ; 10
3 ld de,#0000 ; 10
4 ld bc,#0000 ; 10
pop af ; 10
ret ; 10 / 227
.sv ld (2_B+1),hl ; 16
push af ; 11
pop hl ; 10
ld (1_B+1),hl ; af ; 16
ld (3_B+1),de ; 20
ld (4_B+1),bc ; 20
ret ; 10 /103
ENDM ; 41 bytes for slot , 227 cycles
MACRO reg_slot ; A,HL,DE,BC
call sl_end ; 17+10+79
ld hl,.sv ; 10
ld (sl_end+1),hl ; 16
1 ld a,#00 ; 7
2 ld hl,#0000 ; 10
3 ld de,#0000 ; 10
4 ld bc,#0000 ; 10
ret ; 10 / 179
.sv ld (1_B+1),a ; 13
ld (2_B+1),hl ; 16
ld (3_B+1),de ; 20
ld (4_B+1),bc ; 20
ret ; 10 /79
ENDM ; 36 bytes for slot , 179 cycles
;; пример использования
; создание слотов
sl1 reg_slotF
sl2 reg_slot
sl3 reg_slot
; первичное заполнение (если нужно) - не переключает слот, портит hl для режима с AF
...
call sl1.sv
...
call sl2.sv
...
call sl3.sv
...
; переключение на слот
...
call sl1 ; 17 + 227 = 244 cycles
...
call sl3 ; 17 + 179 = 196 cycles
...
Проверять лень, могу и ошибаться. :v2_wink2:
p.s. Да, при первичном заполнении HL портится ))
p.s.2. Ага, обшибся таки !!!:D - вроде поправил, но это не точно )
p.s.3. Точно неточно. Смещения попутал. Ещё раз поправил ))
добавлю прожорливый по памяти и тактам вариант, который не ломает стек и соответственно не требует запрета прерываний и не занимает ix.
Классный подход с самомодификацией! )) Мне нравится.
Я так понимаю слоты можно переиспользовать изменяя значения в регистрах?
Dart Alver
07.09.2025, 17:24
Я так понимаю слоты можно переиспользовать изменяя значения в регистрах?
Не очень понял. Вызов слота записывает текущие значения в программу текущего слота и вызывает значения из выбранного. Методом слот.sv можно записать текущие значения регистров в программу нужного слота не меняя текущего, но текущий hl - портится для варианта с сохранением флагов.
Ну так должно работать если я опять не ошибся ))
но текущий hl - портится для варианта с сохранением флагов.
Поэтому я пожертвовал ix для чистой записи состояния в сет.
Можно еще расширить вариант и записывать вторые наборы ex af, af'/exx
но это добавит тактов соответственно
Dart Alver
07.09.2025, 17:42
Поэтому я пожертвовал ix для чистой записи состояния в сет.
ну в моём случае это решается просто:
push hl
call слот.sv
pop hl
Можно еще расширить вариант и записывать вторые наборы ex af, af'/exx
но это добавит тактов соответственно
Оно даже этот вариант редко кому нужен, другие уж точно исключительно по необходимости.
Согласен что редко ) Но имеет право быть. Просто попробовать писать в таком стиле и применение найдется.
Заманчиво иметь неограниченное количество регистров ))
К примеру:
Имеем N экземпляров объектов которые нужно изменять на протяжении времени
Основной цикл
{
set1 ; состояние 1
; какие то изменения
...
setN ; состояние N
; аналогично
}
Lethargeek
07.09.2025, 20:15
речь не о многозадачности.
а о чём же? вот ЗАЧЕМ еще такое может понадобиться, чего нельзя проще сделать другими способами?
всего лишь структурирование кода меньше очевидных push/pop (код чище)
с чего меньше? сохранение контекста почти всегда мб нужно только в обработчике прерывания, который обычно всего один
с чего чище? лишний call (+ это кстати лишнее пространство на стеке)
и абстракция расширения основных наборов регистров,
полная бессмыслица, начиная с "расширения набора регистров" программным способом
смысл регистра как раз в том, что это "сверхоперативная", то есть самая БЫСТРАЯ область памяти
а тут медленное сохранение/восстановление всех подряд да еще ix пожертвовать предлагается
то есть выйдет сокращение набора фактически :D
вызов функций использующих свой собственный контекст. общий стек для всех наборов виртуальных регистров позволяет пересылать данные между сетами.
к примеру изменили регистры в сете, переключились на новый, про первый можно забыть и не держать регистры на стеке до вызова процедуры использующей эти регистры, вызвали сет в любом месте программы по надобности.
без многозадачности (реентрабельности) всегда проще сразу с переменными работать, а не со стеком
да еще значительно эффективней - только с теми, которые действительно применяются
Dart Alver
07.09.2025, 21:05
Заманчиво иметь неограниченное количество регистров ))
Заманчиво, но нереально - свапинг их не добавляет а всего лишь переключает, причем медленно. Увы... (
Имеем N экземпляров объектов которые нужно изменять на протяжении времени
Основной цикл
{
set1 ; состояние 1
; какие то изменения
...
setN ; состояние N
; аналогично
}
Ну х.з. состояние такого 'обьекта' должно уложиться в эти несчастные 7 регистров, иначе ещё и стек надо для каждого обьекта свой. Тогда и правда либо многозадачность вертеть, либо как сказал Lethargeek сохраняться в переменных.
В принципе, возможность имеет быть, а целесообразность может подтвердить или опровергнуть только практика. :v2_dizzy_step:
p.s. Я таки оказывается еще ошибся со смещениями - совсем голова не варит, вай-вай-вай :v2_confu:
а о чём же? вот ЗАЧЕМ еще такое может понадобиться, чего нельзя проще сделать другими способами?
Например сместить уровень абстракций от регистров к окнам контекстов функций.
Это автоматически не означает многозадачность или параллельную обработку. Это гибкость кода. Для кого то возможно это великий грех.
с чего меньше? сохранение контекста почти всегда мб нужно только в обработчике прерывания, который обычно всего один
с чего чище? лишний call (+ это кстати лишнее пространство на стеке)
Не согласен что только в обработчике. В кооперативной многозадачности например.
И я не отказываюсь от стека он доступен всем сетам как родной. Можно PUSH в одном сете и POP в другом или том же.
полная бессмыслица, начиная с "расширения набора регистров" программным способом
смысл регистра как раз в том, что это "сверхоперативная", то есть самая БЫСТРАЯ область памяти
а тут медленное сохранение/восстановление всех подряд да еще ix пожертвовать предлагается
то есть выйдет сокращение набора фактически :D
Смысл регистра в том что это быстрая ячейка памяти. Абстракция расширения медленная при переключении, дальше это те же самые быстрые регистры.
без многозадачности (реентрабельности) всегда проще сразу с переменными работать, а не со стеком
да еще значительно эффективней - только с теми, которые действительно применяются
Сохранение загрузка переменных это то же нетривиальная задача. Ты не работаешь с переменными напрямую в памяти, для начала они грузятся в регистры. Мой вариант с sp сокращает время операций и при этом это не полноценный стек push/pop (а сохранил, загрузил другой набор, поработал, вернулся к первому или можно вообще забыть о нем)
- - - Добавлено - - -
Ну х.з. состояние такого 'обьекта' должно уложиться в эти несчастные 7 регистров, иначе ещё и стек надо для каждого обьекта свой. Тогда и правда либо многозадачность вертеть, либо как сказал Lethargeek сохраняться в переменных.
Ну тут свобода, никто не мешает хранить в регистрах сета указатель на структуры данных и работать с ними как с обычными переменными.
Lethargeek
07.09.2025, 22:34
Например сместить уровень абстракций от регистров к окнам контекстов функций.
переменные абстрактнее и удобнее
и зачем нужны "контекстов" без реентрабельности?
Это автоматически не означает многозадачность или параллельную обработку. Это гибкость кода.
что такое "гибкость кода"? в чём измеряется?
Не согласен что только в обработчике. В кооперативной многозадачности например.
...которая на спеке реализуется, ВНЕЗАПНО, через обработчик :D
в чём новизна-то? в лишних call в теле обработчика?
Смысл регистра в том что это быстрая ячейка памяти. Абстракция расширения медленная при переключении, дальше это те же самые быстрые регистры.
то есть в сумме - медленное решение
Сохранение загрузка переменных это то же нетривиальная задача.
ШТА
Ты не работаешь с переменными напрямую в памяти, для начала они грузятся в регистры
да, и что? притом с теми лишь, которые мне нужны
Мой вариант с sp сокращает время операций
для начала - добавляет лишние операции (ибо функция необязательно использует все регистры) + замедляет их еще манипуляциями с sp
и при этом это не полноценный стек push/pop (а сохранил, загрузил другой набор, поработал, вернулся к первому или можно вообще забыть о нем)
то есть медленно, а результаты всё равно потом распихивать в переменные
- - - Добавлено - - -
Согласен со всеми пунктами.
Короткий ответ, я так хочу )
поздно :v2_dizzy_biggrin2:
Lethargeek
08.09.2025, 02:49
Сохранение всего сета регистров — сознательное решение.
Суть не в экономии регистров или минимизации тактов, а в свободе работы с данными.
Каждый решает сам, использовать мой подход или нет — он предоставляет свободу и гибкость, но не навязывает себя.
макрос предоставит больше свободы с гибкостью и без отказа от минимизации/экономии
Swapset - это высокоуровневый подход облегчающий работу с контекстными окнами (отдельными программами), его целью не является оптимизация по скорости.
Для переключения контекстов программ абсолютно не нужен планировщик или организация прерываний с TCB, и даже не задействован стек.
Это именно свобода выбора между ролями процессора в любой момент времени. Свобода забыть и вспомнить о главном цикле, или временно заняться другой задачей. Можно вообще не писать подпрограммы (call/ret) так как каждый сет регистров это новое пространство (как отдельный код запущенный в своем окне).
Здесь нет параллельных вычислений и фонового выполнения как в вытесняющей многозадачности.
Есть возможность запуска любой задачи, независимо от места программы, и возврата при необходимости.
А основная фишка, то, что в любой контекст можно вернуться простым переключением.
Lethargeek
11.09.2025, 23:43
Swapset - это высокоуровневый подход облегчающий работу с контекстными окнами (отдельными программами), его целью не является оптимизация по скорости.
НИЗКОуровневый - ибо ну куда уж ниже простого асма-то
а вот чуть ВЫШЕ уровнем - будет макрос (это намёк))
переменные - также более высокий уровень, чем регистры
ля переключения контекстов программ абсолютно не нужен планировщик или организация прерываний с TCB, и даже не задействован стек.
ну и зачем нужно "переключение контекстов программ" без "организации прерываний"?
если (под)программа не прерывалась, то она работу закончила и результаты из регистров сохранены
Это именно свобода выбора между ролями процессора в любой момент времени. Свобода забыть и вспомнить о главном цикле, или временно заняться другой задачей. Можно вообще не писать подпрограммы (call/ret) так как каждый сет регистров это новое пространство (как отдельный код запущенный в своем окне).
Здесь нет параллельных вычислений и фонового выполнения как в вытесняющей многозадачности.
Есть возможность запуска любой задачи, независимо от места программы, и возврата при необходимости.
А основная фишка, то, что в любой контекст можно вернуться простым переключением.
словоблудие бессмысленное какое-то :v2_wacko: приведи КОНКРЕТНЫЕ сценарии применения!
вот чисто технически для начала - каким образом ты без прерываний (!) собрался запускать (а также возвращаться) с любого (!) места? :v2_dizzy_facepalm:
Rubts0FF
11.09.2025, 23:49
Идея и решение интересные, вот практическое применение, пока не знаю .., таких идей-задач никогда не возникало. Надо подумать как это пристроить к делу.
ну и зачем нужно "переключение контекстов программ" без "организации прерываний"?
если (под)программа не прерывалась, то она работу закончила и результаты из регистров сохранены
ну это если проще нажатие Alt+Tab )) только в цикле программы.
НИЗКОуровневый - ибо ну куда уж ниже простого асма-то
а вот чуть ВЫШЕ уровнем - будет макрос (это намёк))
переменные - также более высокий уровень, чем регистры
Именно это и делает SwapSet более «высокоуровневым» подходом: программист концентрируется на логике задач, а не на механике push/pop и сохранении состояний. Основная идея кода становится центральной, а «чем занят цикл и как сохранить регистры» — детали, о которых заботится SwapSet.
По сути, это абстракция контекста: ты создаёшь несколько «виртуальных ролей» процессора и переключаешься между ними мгновенно, словно работаешь в отдельных окнах памяти, где каждый сет — своё пространство.
- - - Добавлено - - -
вот чисто технически для начала - каким образом ты без прерываний (!) собрался запускать (а также возвращаться) с любого (!) места?
чисто технически все описано в коде библиотеки
// SwapSet.asm
// 9/6/2025
//
// Needs 8 bytes per structure:
//
// RegSet dw 0 //AF
// dw 0 //BC
// dw 0 //DE
// dw 0 //HL
// End
//
// The CurrentSet variable must be initialized
// prior to first use for the mechanism to function correctly.
//
InitSwapSet
ld ix,RegSet0+8 //!!!
ld (CurrentSet),ix //!!!
ld ix,RegSet0
call SwapSet
ret
//in ix- register set address
SwapSet di
ld (StoreSP),sp
//SaveSet
ld sp,(CurrentSet) // Current Set StackEnd
push hl
push de
push bc
push af
//LoadSet
ld sp,ix //Address New Set
pop af
pop bc
pop de
pop hl
ld (CurrentSet),sp
ld sp,(StoreSP)
ei
ret
//------------------------------------------------
SwapSet0 ld ix,RegSet0
jr SwapSet
SwapSet1 ld ix,RegSet1
jr SwapSet
SwapSet2 ld ix,RegSet2
jr SwapSet
SwapSet3 ld ix,RegSet3
jr SwapSet
//------------------------------------------------
StoreSP dw 0
CurrentSet dw RegSet0+8
//---------------------------------------
RegSet0 dw 0 //AF
dw 0 //BC
dw 0 //DE
dw 0 //HL
RegSet1 ds 8
RegSet2 ds 8
RegSet3 ds 8
- - - Добавлено - - -
Идея и решение интересные, вот практическое применение, пока не знаю .., таких идей-задач никогда не возникало. Надо подумать как это пристроить к делу.
Я сам активно ищу применение )) Но идея захватила. Есть определенное сходство с Окнами регистров в архитектуре SPARC, но там это бесплатно по тактам.
- - - Добавлено - - -
словоблудие бессмысленное какое-то приведи КОНКРЕТНЫЕ сценарии применения!
Пример с тремя копиями текстового редактора
Представим три экземпляра редактора: Edit1, Edit2, Edit3.
Классический подход:
Для переключения между редакторами нужно:
Сохранять все регистры и указатели.
Сохранять локальные переменные в память.
Загружать новое состояние для выбранного редактора.
Каждый раз, когда меняешь редактор, приходится делать много операций сохранения/восстановления.
SwapSet-подход:
Для каждой копии редактора создаётся свой SwapSet: SwapSet0, SwapSet1, SwapSet2.
Переключение делается одной инструкцией:
call SwapSet0 ; активировать Edit1
call SwapSet1 ; активировать Edit2
call SwapSet2 ; активировать Edit3
Все регистры и состояние уже сохранены внутри SwapSet.
Никакой повторной инициализации не требуется — редактор просто продолжает работу с тем же состоянием.
Lethargeek
13.09.2025, 00:08
ну это если проще нажатие Alt+Tab )) только в цикле программы.
нда, похоже, представление об устройстве и функционировании спека примерно такое же никакое, как и о работе alt-tab в винде
Именно это и делает SwapSet более «высокоуровневым» подходом: программист концентрируется на логике задач, а не на механике push/pop и сохранении состояний. Основная идея кода становится центральной, а «чем занят цикл и как сохранить регистры» — детали, о которых заботится SwapSet.
если программист способен логику задач на асме писать, вся подобная "механика" для него проблемой точно не будет :v2_dizzy_biggrin2:
По сути, это абстракция контекста: ты создаёшь несколько «виртуальных ролей» процессора и переключаешься между ними мгновенно, словно работаешь в отдельных окнах памяти, где каждый сет — своё пространство.
по сути это небольшой кусок типичного кода обработчика прерываний, непонятно зачем ухудшенный и непонятно как вызываемый
чисто технически все описано в коде библиотеки
чисто технически вопрос ты опять не понял
Пример с тремя копиями текстового редактора
Представим три экземпляра редактора: Edit1, Edit2, Edit3.
Классический подход:
Для переключения между редакторами нужно:
Сохранять все регистры и указатели.
Сохранять локальные переменные в память.
Загружать новое состояние для выбранного редактора.
Каждый раз, когда меняешь редактор, приходится делать много операций сохранения/восстановления.
SwapSet-подход:
Для каждой копии редактора создаётся свой SwapSet: SwapSet0, SwapSet1, SwapSet2.
Переключение делается одной инструкцией:
call SwapSet0 ; активировать Edit1
call SwapSet1 ; активировать Edit2
call SwapSet2 ; активировать Edit3
Все регистры и состояние уже сохранены внутри SwapSet.
Никакой повторной инициализации не требуется — редактор просто продолжает работу с тем же состоянием.
:v2_dizzy_facepalm::v2_dizzy_facepalm::v2_dizzy_fa cepalm::v2_dizzy_tired2:
ну вот, допустим, работает код любого экземпляра Edit1, Edit2, Edit3 - а теперь внимание, вопрос:
каким образом "независимо от места программы" происходит переход на нужный call SwapSetX - ?
...особенно "без организации прерываний" :v2_dizzy_messed:
по сути это небольшой кусок типичного кода обработчика прерываний, непонятно зачем ухудшенный и непонятно как вызываемый
Именно в этом и разница. Обработчик прерываний всегда запускается асинхронно и требует жёсткого сохранения/восстановления контекста, потому что точка входа задаётся внешним событием.
SwapSet работает иначе: это синхронный механизм переключения, инициируемый самой программой. Здесь не нужно улавливать внешний сигнал — программист сам решает, в какой момент «перепрыгнуть» на другой набор регистров.
Поэтому вопрос «как это возможно без прерываний» на самом деле снимается:
прерывания нужны для внезапного переключения;
SwapSet нужен для сознательного переключения.
Это разные сценарии, но решают одну задачу — быстрое сохранение/восстановление контекста.
Что даёт SwapSet «сверх обычного push/pop»:
минимальный и фиксированный по времени код (без ручного сохранения переменных);
простая абстракция «виртуальные роли процессора» — можно держать несколько экземпляров программы (например, редакторов) и переключаться между ними одной инструкцией;
никакой привязки к обработчику IRQ — разработчик сам ставит точку переключения.
Если коротко:
Прерывания = «Alt+Tab, когда ОС сама решает переключить».
SwapSet = «Alt+Tab, когда я сам жму комбинацию».
Оба механизма нужны, просто применяются в разных случаях.
- - - Добавлено - - -
ну вот, допустим, работает код любого экземпляра Edit1, Edit2, Edit3 - а теперь внимание, вопрос:
каким образом "независимо от места программы" происходит переход на нужный call SwapSetX - ?
...особенно "без организации прерываний"
берешь и пишешь в исходнике:
; в любой точке программы
ld ix, Edit3
call SwapSet
; и ты уже в Edit3, с восстановленными регистрами
Lethargeek
13.09.2025, 13:42
wapSet работает иначе: это синхронный механизм переключения, инициируемый самой программой. Здесь не нужно улавливать внешний сигнал
с чем "синхронный", с логикой задачи? тогда так про любые переходы можно сказать :D
— программист сам решает, в какой момент «перепрыгнуть» на другой набор регистров.
осталось понять, зачем делать это в наиболее неподходящие моменты на самом низком уровне (но при этом декларируя стремление к высокой абстракции) да еще и жертвовать регистрами ради этого
Если коротко:
Прерывания = «Alt+Tab, когда ОС сама решает переключить».
SwapSet = «Alt+Tab, когда я сам жму комбинацию».
крайне неудачные аналогии, да и работает "alt-tab" посложнее, чем представляешь
ну да ладно, углубляться сейчас в это очень не хочется))
берешь и пишешь в исходнике:
Код:
; в любой точке программы
ld ix, Edit3
call SwapSet
; и ты уже в Edit3, с восстановленными регистрами
ну наконец-то... только это же как раз наоборот, получается ЗАВИСИМО "от места программы" - куда (не) впишут :D
внесли ясность, а теперь берём и возвращаемся к первоначальным вопросам:
ЗАЧЕМ вписывать выход из задачи в такие НЕУДОБНЫЕ точки, где нужно сохранять значения всех регистров?
почему не делать этого между законченными блоками расчётов после сохранения результатов в память?
особенно если хочется "высоких абстракций" (ведь даже отдельные переменные куда выше уровнем, чем регистры)
в наше время даже для дебага мелких процедурок это бессмысленно, эмули удобнее однозначно
тут как бы дело в том что если кому то и понадобится данный функционал, его напишут за пару минут, тут нет ни хитростей ни обьема. это уровень очистки лдиром. Посвящать такой мелочи тему странно. Собственно она вся и состоит из объяснения что это нафиг не нужно.
Любой нестандартный подход рассматривается как ересь, людьми которые привыкли писать в своей парадигме.
Я не критикую классические стандартные подходы, я лишь попытался предложить поэкспериментировать, сломать косность и включить фантазию.
К сожалению с годами практики, все необычные решения начинают восприниматься в штыки, даже среди профессионалов.
Кроме аргументов, "что это нафиг никому нее нужно" и "можно сделать проще" не вижу никаких обоснований. Запретить, выставить на посмешище, указать на свое место, все, что я слышу в ответ на простое предложение попробовать и оценить в деле.
Я изначально не навязывал свое видение, но на протяжении обсуждения, зачем то вынужден оправдываться. Я пишу так как я хочу, если кто-то думает так, как я, значит кому то зашла идея. Раскритиковать можно что угодно, для этого нужно еще меньше усилий чем написать простой алгоритм.
К примеру: традиционный подход с прерываниями абсолютно неэкономично расходует ресурсы на сохранение/восстановление контекстов задач. С надобностью или без каждые 1/50 секунды сохраняется и восстанавливается задача работающая в фоне. Мой пример с переключением так же можно повесить на опрос клавиатуры и вызывать как подзадачу только если это действительно нужно. Да это привязка к прерываниям, но уже другая.
А если смотреть в целом, это именно свобода. У тебя появляется возможность работать с виртуальными наборами, не заморачиваясь на других реализациях. Да это очень простой код, но это не значит что он примитивный.
Если кому то нравится работать с макросами, никто не мешает оформить переключение в виде шаблона вместо вызовов call/ret.
Если вы категорически против, просто пройдите мимо.
Lethargeek
15.09.2025, 00:40
Любой нестандартный подход рассматривается как ересь, людьми которые привыкли писать в своей парадигме.
не льсти себе - тут вопросы вовсе не к "нестандартности", а к твоему уровню знаний прежде всего, явно малому для такой самоуверенности
ведь "стандартные подходы" именно такие не просто так, они развивались и оттачивались годами, множеством людей, отбросивших множество вариантов
...и тут приходит некто весь в белом и, подёргав незапертую дверь не в ту сторону, предлагает "нестандартно" к форточке пристроить крыльцо
К примеру: традиционный подход с прерываниями абсолютно неэкономично расходует ресурсы на сохранение/восстановление контекстов задач. С надобностью или без каждые 1/50 секунды сохраняется и восстанавливается задача работающая в фоне. Мой пример с переключением так же можно повесить на опрос клавиатуры и вызывать как подзадачу только если это действительно нужно. Да это привязка к прерываниям, но уже другая.
https://vgif.ru/gifs/163/vgif-ru-1095.gif
Концепция SwapSet vs регистровые окна SPARC
SPARC
В архитектуре SPARC регистры организованы в окна: при вызове функции активируется новое окно.
Каждое окно делится на три области:
in — аргументы, полученные от вызывающей стороны
local — собственные локальные переменные функции
out — аргументы для следующего вызова
При переключении окна:
out-регистры вызывающей функции становятся in-регистрами вызываемой
локальные сохраняются внутри окна
стек используется только если регистров не хватает или при переполнении окон
SwapSet
Реализация на Z80 через сохранение/восстановление фиксированных наборов регистров.
Каждый набор (set) изолирован: переключение через
call SwapSet скрывает все локальные регистры предыдущего набора.
Аргументы передаются не через память, а напрямую в регистрах нового набора после переключения:
ld ix, FunctionSet
call SwapSet ; открыть контекст функции
ld hl, arg1
ld de, arg2 ; сразу положили аргументы в "in"-регистры
call Function
После возврата восстановление идёт простым переключением обратно:
ld ix, MainSet
call SwapSet ; вернулись в локальный набор вызывающего
Сходство
Локальные регистры изолированы и не видны другой стороне.
Аргументы передаются напрямую через выделенную часть регистров, без копирования в память.
Вызов функции становится быстрым и компактным.
Различие
У SPARC окна жёстко встроены в аппаратную модель, переключение мгновенное и без кода.
В SwapSet требуется программный вызов
call SwapSet, что добавляет оверхед.
В SPARC есть «скользящие» окна (out → in), а в SwapSet это нужно явно организовать договорённостью: какие регистры считать аргументами.
Таким образом, SwapSet — это софтверная имитация механизма окон SPARC, где локальные регистры полностью изолированы, а аргументы задаются прямо в регистрах нового контекста.
Вызов функции становится быстрым и компактным.
https://i.imgur.com/sSXmTIM.jpeg
Lethargeek
15.09.2025, 23:15
Ваш высокомерный тон
сказал некто, представляющий себя чуть ли не пророком, противостоящим косным инквизиторам и гонителям :rolleyes:
скорее говорит о попытке самоутвердиться за счёт новичков (или тех, кого вы такими считаете), чем о настоящем профессионализме.
Я не претендую на абсолютную истину и не запрещаю пользоваться «отточенными подходами». Но и право на собственные эксперименты никто не отменял. Если вам это кажется ерундой — просто пройдите мимо, без демонстрации пренебрежения.
Это открытый форум, а не личный фан-сайт, на котором можно самоутверждаться до посинения. Выступаешь с предложением чего-либо - будь готов получить вопросы и критику. Изобрёл велосипед - обоснуй квадратные колёса. Не согласен, не умеешь обосновать? Так никто не запрещает мимо пройти.
SPARC
как что-то хорошее
Аргументы передаются не через память, а напрямую в регистрах нового набора после переключения:
ld hl, arg1
ld de, arg2 ; сразу положили аргументы в "in"-регистры
call Function
Так это же "стандартный подход" и есть :D (один из, старше чем сам z80). Только, уж конечно, без неэффективных "переключений" - когда нужно, либо сохраняют значения регистров в стек/переменные, либо ниже прямо в ld-команды.
Так это же "стандартный подход" и есть (один из, старше чем сам z80). Только, уж конечно, без неэффективных "переключений" - когда нужно, либо сохраняют значения регистров в стек/переменные, либо ниже прямо в ld-команды.
А я не спорю, что это стандартный подход. Я просто взял уровень выше, что если бы у z80 было больше регистров? Это не оптимизация, это эксперимент, я уже об этом говорил. И сравнение со SPARC и окнами регистров появилось случайно, только потому что обнаружилось некоторое сходство в подходе. Я не претендую на то что это прямо уж революция. Неужели никому не было интересно оторваться от того что есть и представить другую архитектуру для z80.
Мне вот интересно написать транслятор байткода для z80 например. Из преимуществ полная перемещаемость и компактность, даже по сравнению с нативным кодом. Да я может еще более гик чем вы.
Очень интересно, но нифика непонятно...
Зачем заменять push/pop или ld (nn),reg/ld reg,(nn) на процедуру, которая занимает место это раз, и тратит туеву кучу тактов это два?
Да еще портит индексный регистр...
Даже если предположить некую "многозадачность", то тут уж сохранять все регистры, включая индексные и SP, назначать для каждой задачи свой стек, да до кучи сохранять общие для задач переменные.
А такой способ... Ну такое себе. Трата памяти и тактов.
- - - Добавлено - - -
речь не о многозадачности. всего лишь структурирование кода меньше очевидных push/pop (код чище)
Видать большие проекты не писали? Когда идет гонка за каджым байтом/тактом, а зачастую и за тем, и за другим одновременно?
Очень интересно, но нифика непонятно...
Зачем заменять push/pop или ld (nn),reg/ld reg,(nn) на процедуру, которая занимает место это раз, и тратит туеву кучу тактов это два?
Да еще портит индексный регистр...
Даже если предположить некую "многозадачность", то тут уж сохранять все регистры, включая индексные и SP, назначать для каждой задачи свой стек, да до кучи сохранять общие для задач переменные.
А такой способ... Ну такое себе. Трата памяти и тактов.
В том то и дело, что мне это тоже интересно) Когда ты используешь push/pop ты занял стек и тебе нужно помнить,что ты туда положил, чтобы потом вынуть. В данном методе стека как такового нет. Регистры хранятся в кусочках памяти по 8 байт и к ним всегда можно вернуться переключением, а вот это самое интересное. Если не думать о стеке, то в памяти можно хранить неограниченное количество контекстов к которым можно возвращаться простым переключением по имени сета регистров.
Для чего это использовать? Для всего что подскажет фантазия.
здесь не спроста использованы инструкции push и pop для сохранения и восстановления регистров они короче по тактам чем прямое сохранение/восстановление из памяти. (10-11 против 16-20) а индексный регистр задействован для чистоты чтобы сохранялся основной набор. и это не push/pop это именно swap более медленный аналог exx, ex af,af'. его единственное преимущество неограниченные наборы основных регистров, которые можно выбирать в любой момент.
- - - Добавлено - - -
Видать большие проекты не писали? Когда идет гонка за каджым байтом/тактом, а зачастую и за тем, и за другим одновременно?
писали. а сейчас просто экспериментирую. в этом и есть косность мышления когда ты себя начинаешь ограничивать, устанавливать правила при написании кода. это не дает мыслить свободно и твой код становится, как ни грустно, типичным ((
стэк это push/pop 2 байта/21 такт на пару
память это ld (nn),reg/ld reg,(nn) это 6 байт/32(26) тактов для HL, для DE/BC несколько больше
ваш вариант ld ix,addr: call Swap это 7 байт/31 такт только на вызов процедуры, плюс сотня другая тактов займет сама процедура, плюс порча ix
сохранение af,bc,de,hl одномоментно нужно очень редко
И какой вариант лучше? Думаю тот что занимается меньше места и быстрее
И в чём пробедема думать о стеке? Об этом думать всё равно придётся.
- - - Добавлено - - -
В данном методе стека как такового нет
Стэк есть всегда
Стэк есть всегда
это не догма. я им жонглирую в своем примере.
сохраняется общий стек для всех наборов, но push/pop для регистров отсутствует.
- - - Добавлено - - -
стэк это push/pop 2 байта/21 такт на пару
память это ld (nn),reg/ld reg,(nn) это 6 байт/32(26) тактов для HL, для DE/BC несколько больше
ваш вариант ld ix,addr: call Swap это 7 байт/31 такт только на вызов процедуры, плюс сотня другая тактов займет сама процедура, плюс порча ix
сохранение af,bc,de,hl одномоментно нужно очень редко
И какой вариант лучше? Думаю тот что занимается меньше места и быстрее
я не спорю что можно написать короче и быстрее. я предложил механизм. если интересно оптимизировать можно всегда.
Мой подход:
минусы:
неэффективно по скорости
плюсы:
памяти - копейки (8байт на сет)
количество наборов регистров не ограниченно (ограниченно только свободной памятью)
изоляция регистров в сете (они не испортятся при вызове функции из другого сета)
переключение между сетами элементарное
Бесит реклама, а этот пост просто ей пропитан.
Вот жаль нельзя влепить огромный такой минус за рекламу никому ненужной процедуры (как заметили выше на либу ну это никак не тянет), которая медленнее, длиннее, портит индексный регистр.
компактный контейнер для переменных
бред
все переменные игрока хранятся в одном блоке памяти (8 байт)
как громко. все переменные игрока, аж 8 байт
Возврат к предыдущему состоянию в текстовых редакторах
ну да, ну да, в 8 байт так дофига можно вместить, что аж последнее состояние редактора
Ограниченный размер стека.
ага, чем ограничен то? ну разве что памятью всего 64кб её у нас
и т.д. и т.п.
вобщем весь этот бред даже комментить не буду
- - - Добавлено - - -
З.Ы. ох как нехорошо после моего коммента вычищать портянку своего поста до двух куцых абзацев
еще бы один минус в карму влепил бы за это
Bedazzle
16.09.2025, 10:58
Но потенциал у идеи я считаю все же есть. Просто нужно найти применение.
Лучше один раз сделать, чем сто раз сказать.
Лучше один раз сделать, чем сто раз сказать.
воистину!
Какой-то беспредметный $pa4, ну если чего не так - ну ведь во "Флейм" можно перекинуть эту тему вроде - и пусть себе будет...
Вернемся к сути:
SwapSet — изоляция регистров на Z80
Идея эксперимента: реализовать механизм изоляции регистров и создания виртуальных наборов регистров на Z80
Суть метода:
Каждый набор регистров ("сет") хранится в памяти как блок из 8 байт (AF, BC, DE, HL).
Команда
SwapSet сохраняет текущие значения регистров в активный блок и загружает значения из нового блока.
Таким образом, вместо push/pop или обращения к стеку мы получаем мгновенную изоляцию контекстов.
Плюсы:
Неограниченное количество наборов регистров (ограничение только по памяти).
Простое переключение между контекстами по имени:
ld ix, SetName
call SwapSet
Локальные регистры изолированы: функция работает в своём наборе, не трогая основной.
Возможность прямой передачи аргументов в регистры нового сета без обращения к памяти:
ld ix, FunctionSet
call SwapSet
ld hl, arg1
ld de, arg2
call Function
Минусы:
Медленнее по тактам, чем классический push/pop.
Задействует индексный регистр IX.
Увеличенный код вызова (
ld ix, ...; call SwapSet).
Применимость:
Эксперименты с архитектурой Z80.
Альтернативное структурирование кода без жёсткой привязки к стеку.
Потенциальная основа для байткод-машины с полной изоляцией контекстов.
Пример использования:
ld ix, MainSet
call SwapSet ; активируем основной набор
ld ix, FunctionSet
call SwapSet ; переключаемся на набор функции
ld hl, arg1
ld de, arg2
call Function ; вызов функции с аргументами в регистрах
ld ix, MainSet
call SwapSet ; возвращаемся к основному набору
товарищ ниппер, смотрите, пока не касаемся факта что свя эта тема бред. В начале вам культурно намекали что функционал бесполезен чуть более чем полностью, культурно так как умение расставлять команды в рабочие (но не всегда нужные) последовательности это уважаемый всеми скилл. Но вы упорно, в припадке маниакального бреда, пытаетесь доказать что белое это фиолетовое. Отсюда и реакция, вы просто всем доказали что ничего тяжелее... ничего сложнее примеров из как написать игру 92года не писали. Остановитесь, ладно я известный кретин и бездельник, вялый тоже не особо продуктивен, но LW это авторитет для всех тут, поверьте у него релиженого кода нам столько за всю пенсию не написать. Прислушайтесь что говорят люди с опытом, гланды через горло не просто так вырезают сотни лет.
ладно, понятно, никогда не сдавайся, позорься до конца!
null_device
16.09.2025, 22:58
Любой нестандартный подход рассматривается как ересь, людьми которые привыкли писать в своей парадигме.
Я не критикую классические стандартные подходы, я лишь попытался предложить поэкспериментировать, сломать косность и включить фантазию.
С подключением!
Программно-аппаратная архитектура z80 (ноги которого растут из i8080) в целом, и zx-spectrum в частности - не особо располагает к параллельному программированию.
Максимум - квазипараллельная многозадачность, за счёт прерываний. Где сама висящая на этом прерывании программа, должна заботиться о сохранении/восстановлении набора регистров, которые она использует при своем выполнении.
....
Очевидной наглядности и удобства, хранение значений в виртуальных наборах регистров - я не вижу. Банальная задача обмена данными между значениями виртуальных регистров из разных наборов - превращается в перекладывание из одного кармана в другой.
Суета - ради суеты.
С подключением!
Программно-аппаратная архитектура z80 (ноги которого растут из i8080) в целом, и zx-spectrum в частности - не особо располагает к параллельному программированию.
Максимум - квазипараллельная многозадачность, за счёт прерываний. Где сама висящая на этом прерывании программа, должна заботиться о сохранении/восстановлении набора регистров, которые она использует при своем выполнении.
....
Очевидной наглядности и удобства, хранение значений в виртуальных наборах регистров - я не вижу. Банальная задача обмена данными между значениями виртуальных регистров из разных наборов - превращается в перекладывание из одного кармана в другой.
Суета - ради суеты.
Вот это уже по существу! )) Спасибо!
- - - Добавлено - - -
А как насчет подхода, не как к многозадачности, а именно изоляции регистров для каждого участника вычислений (функции или даже методы, локальные переменные)? Для всех юнитов свой собственный набор изолированный от других, именно когда возникает необходимость обмена данными и появляется "перекладывание из кармана в карман".
- - - Добавлено - - -
Программно-аппаратная архитектура z80 (ноги которого растут из i8080) в целом, и zx-spectrum в частности - не особо располагает к параллельному программированию.
Изначально z80 не умеет плавающую точку, но это не помешало реализовать это программмно. И есть куча примеров игр со сложной графикой. (И да, я знаю про таблицы)
- - - Добавлено - - -
Изолированные функции — это концепция, когда каждая функция имеет свой собственный набор регистров и данных, полностью изолированный от других функций.
В обычном коде на Z80:
Когда функция вызывается, часто нужно сохранять регистры через PUSH и восстанавливать через POP, чтобы не испортить данные вызывающей функции.
Если регистров мало, приходится тщательно следить, кто что использует — легко ошибиться.
С изолированными функциями:
Каждая функция имеет свой виртуальный набор регистров, например блок памяти на 8 байт для AF, BC, DE, HL.
При входе в функцию выполняется SwapSet — текущие регистры сохраняются в активный набор, загружаются регистры функции.
Функция работает так, будто у неё есть собственные регистры, полностью независимые от остального кода.
После завершения вызывается обратный SwapSet — регистры возвращаются в прежнее состояние.
Плюсы такого подхода
Изоляция: Никакие изменения внутри функции не трогают регистры других функций.
Чистый код: Нет постоянного PUSH/POP внутри функций, код проще читать.
Предсказуемость: Функция полностью управляет своим контекстом.
Модульность: Легко переносить функции в другие проекты, не ломая основной код.
Минусы
Скорость: Переключение контекста через SwapSet медленнее обычного PUSH/POP.
Память: Каждый набор регистров занимает место в RAM.
Управление: Нужно продумывать, как передавать данные между контекстами.
Применение
Сложные процедуры, где важна чистота кода, а не каждый такт.
Функции с большим количеством локальных данных (например, игровые объекты или драйверы устройств).
Экспериментальные архитектуры на Z80, прототипы байткод-машин с изоляцией контекстов.
Проще говоря: это как иметь свои «личные регистры» для каждой функции, почти как классы на Z80, только без полноценного ООП.
P.S.
Подумал как раз: Изолированная функция не обязана возвращать результат. Это может быть функционал "под капотом". К примеру как функция ядра. Она работает но мы видим эту работу лишь косвенно.
Максимум - квазипараллельная многозадачность, за счёт прерываний. Где сама висящая на этом прерывании программа, должна заботиться о сохранении/восстановлении набора регистров, которые она использует при своем выполнении.
Рабочий вариант - делал для AVR когда-то очень давно на ассемблере - работало как часики)))
Dart Alver
17.09.2025, 02:03
Согласен. Вместо того чтобы обсудить предложенную тему изоляции регистров и дальнейшего развития предложенного подхода, повылазили процедурно-ориентированные деды, считающие каждый такт (байт) и не видящие за деревьями леса. Подоставали свои афигительные старинные коллекции гифок и начали гадить в чужой топик, показывая свою «суперкомпетентность» в написании кода.
Так по сути то и обсудили - идея не пошла в массы ))
На уверения в том, что предложения топикстартера являются всего лишь экспериментом, последовало предложение завести собственный фэн-клуб и выпендриваться (самоутверждаться) там. Я понимаю, что в силу возраста разумное поведение — это последнее, что можно ожидать от таких индивидов.
Эксперименты это хорошо, но они однако требуют какого-то результата. Пока результат никакой.
Применимость:
Эксперименты с архитектурой Z80.
Альтернативное структурирование кода без жёсткой привязки к стеку.
Потенциальная основа для байткод-машины с полной изоляцией контекстов.
Архитектуру Z80 вы ни на грамм не изменили увы. Привязка к стеку чуть уменьшилась, но цена пока что великовата будет. Байт-код машина - кто и зачем её делать будет и как бы при таком раскладе её бейсик по эффективности не превзошёл, но тут конечно только код покажет которого нет.
А как насчет подхода, не как к многозадачности, а именно изоляции регистров для каждого участника вычислений (функции или даже методы, локальные переменные)? Для всех юнитов свой собственный набор изолированный от других, именно когда возникает необходимость обмена данными и появляется "перекладывание из кармана в карман".
Пока что вы из кармана в карман из поста в пост перекладываете одни и те же аргументы и один и тот же код. Развития не наблюдается , библиотеки тоже. Будет что-либо весомое в формате кода, тогда и можно будет обсуждать, а пока это рассуждение на тему как запрячь в сани сферического коня в вакууме, если конь воображаемый а сани нарисованные ))
Так по сути то и обсудили - идея не пошла в массы ))
а я бы так не сказал. (тролли - это не массы). но мы еще увидим
Эксперименты это хорошо, но они однако требуют какого-то результата. Пока результат никакой.
хотя бы концепция изолированных функций. осталось проверить на практике )
Архитектуру Z80 вы ни на грамм не изменили увы. Привязка к стеку чуть уменьшилась, но цена пока что великовата будет. Байт-код машина - кто и зачем её делать будет и как бы при таком раскладе её бейсик по эффективности не превзошёл, но тут конечно только код покажет которого нет.
байткод машину я уже разрабатываю. есть старый прототип и он явно быстрее BASIC. архитектуру железо я и не претендую изменить, можно изменить лишь традиционный подход.
Пока что вы из кармана в карман из поста в пост перекладываете одни и те же аргументы и один и тот же код. Развития не наблюдается , библиотеки тоже. Будет что-либо весомое в формате кода, тогда и можно будет обсуждать, а пока это рассуждение на тему как запрячь в сани сферического коня в вакууме, если конь воображаемый а сани нарисованные ))
сам код очень простой. единственно что можно уже будет добавить примеры с теми же функциями. ну возможно я или кто то оптимизирует SwapSet который все так ругают за прожорливость))
В любом случае, спасибо огромное за внимание к проекту!)
Lethargeek
17.09.2025, 04:18
Изолированные функции — это концепция, когда каждая функция имеет свой собственный набор регистров и данных, полностью изолированный от других функций.
халва-халва-халва :v2_sick: нет у функций никаких "собственных наборов регистров", есть локальные переменные - временные и статические (сохраняемые в памяти после выхода)
а набор регистров, хоть ты тресни, один на всех (из них некоторые иногда могут применяться глобально на довольно долгом промежутке работы или даже постоянно, как iy в бейсике)
зачем с памятью локальных переменных работать "нестандартным" и неэффективным способом, когда "изоляцию" прекрасно обеспечивают "стандартные" - тайна велика есть
null_device
17.09.2025, 05:17
.В обычном коде на Z80:
Когда функция вызывается, часто нужно сохранять регистры через PUSH и восстанавливать через POP, чтобы не испортить данные вызывающей функции.
Если регистров мало, приходится тщательно следить, кто что использует — легко ошибиться.
С изолированными функциями:
Каждая функция имеет свой виртуальный набор регистров, например блок памяти на 8 байт для AF, BC, DE, HL.
При входе в функцию выполняется SwapSet — текущие регистры сохраняются в активный набор, загружаются регистры функции.
Функция работает так, будто у неё есть собственные регистры, полностью независимые от остального кода.
После завершения вызывается обратный SwapSet — регистры возвращаются в прежнее состояние.
Если мне не изменяет склероз, под капотом з80 - имеется штатный функционал для переключения на встроенный в чип, альтернативный набор регистров, и обратно (две команды на ассемблере).
Для большинства задач - этого более чем, достаточно.
ZXPIRATE
17.09.2025, 16:29
При входе в функцию выполняется SwapSet — текущие регистры сохраняются в активный набор, загружаются регистры функции.
При входе в функцию делаешь push только тех регистров, которые используются и экономишь время и память
Чистый код: Нет постоянного PUSH/POP внутри функций, код проще читать.
В функции может быть много push-pop поскольку количества регистров зачастую не хватает, ты предлагаешь вместо одного пуша пушить сразу все регистры?
Изоляция: Никакие изменения внутри функции не трогают регистры других функций.
Что мешает программе обратиться к ячейке памяти, в которой у тебя сохранёнка лежит и изменить её?
Чистый код: Нет постоянного PUSH/POP внутри функций, код проще читать.
Ага, только постоянные ld ix,... и call-ы
Изначально z80 не умеет плавающую точку, но это не помешало реализовать это программмно. И есть куча примеров игр со сложной графикой. (И да, я знаю про таблицы)
Ты тот чел с двача?
Overhead прерываний vs. Overhead SwapSet
Аргумент критиков: «SwapSet слишком медленный»
Давайте посмотрим на цифры.
---
1. Разовый overhead
ПараметрПрерываниеSwapSet
Время переключения~150 тактов~192 такта
Разница — всего ~40 тактов..
---
2. Суммарный overhead
ПараметрПрерываниеSwapSet
Частота вызова50 раз/сек (кадр) или 1000+ раз/сек (таймер)Несколько раз за программу
Суммарные траты7 500 тактов/сек (50 Гц) или 150 000 тактов/сек (1 кГц)~1000 тактов за всё время работы
Здесь всё очевидно: прерывания стабильно «съедают» тысячи тактов каждую секунду, SwapSet же — копейки за весь прогон программы.
---
3. Разный контекст использования
• Прерывания — вынужденная плата за связь с железом. Все смирились: «так уж устроен Z80».
• SwapSet — осознанный инструмент. Его сразу критикуют: «зачем так дорого?»
На деле же и там, и там сохраняется/восстанавливается состояние ради изоляции контекста.
---
4. Цель SwapSet
SwapSet — не для tight loop и не для экономии каждого такта. Его задача:
• изолировать функции друг от друга;
• дать каждой задаче «свои регистры»;
• сделать код предсказуемым и модульным.
Цена в 192 такта оправдана, если цель — ясность и надёжность архитектуры.
---
5. Итоговый контраргумент
Если сообщество спокойно мирится с 7 500+ тактами overhead от прерываний каждую секунду, то почему вдруг 192 такта на SwapSet, вызываемый пару раз за программу, объявляется непозволительной роскошью? Это ровно тот же приём — сохранение состояния ради изоляции, только на уровне приложений, а не железа.
ZXPIRATE
18.09.2025, 10:35
Давай, в качестве доказательства работы твоего гениального метода, ты напишешь простейший "Hello, world" на асме, без использования функций из пзу.
Lethargeek
18.09.2025, 11:19
Давайте посмотрим на цифры.
а давайте сначала научимся правильно считать
1. Разовый overhead
Параметр Прерывание SwapSet
Время переключения ~150 тактов ~192 такта
не "~192", а "от 223" (не забываем стоимость вызова + еще, возможно, альтернативные)
не "~150", а может быть и намного меньше (сохранение не сразу, ранние проверки и выход)
2. Суммарный overhead
Параметр Прерывание SwapSet
Частота вызова 50 раз/сек (кадр) или 1000+ раз/сек (таймер) Несколько раз за программу
Суммарные траты 7 500 тактов/сек (50 Гц) или 150 000 тактов/сек (1 кГц) ~1000 тактов за всё время работы
Здесь всё очевидно: прерывания стабильно «съедают» тысячи тактов каждую секунду, SwapSet же — копейки за весь прогон программы.
эээ, минуточку! как это "несколько раз за программу"?
почему количество переключений "независимо от места" оказалось вдруг НАСТОЛЬКО ограничено?
что за новости, почему ВНЕЗАПНО стало нельзя иметь over 9000 "независимых мест" в секунду?? :v2_mad:
Цель SwapSet
SwapSet — не для tight loop и не для экономии каждого такта. Его задача:
• изолировать функции друг от друга;
• дать каждой задаче «свои регистры»;
• сделать код предсказуемым и модульным.
ломишься в открытую дверь, это всё и так уже есть
Цена в 192 такта оправдана, если цель — ясность и надёжность архитектуры.
продолжение бессмысленной трескотни :v2_sick: в каких единицах измеряется "ясность и надёжность архитектуры"?
и да, кстати, di в произвольном месте это лишний риск пропуска прерывания, всякие музончики пострадают - просто охренительная "надёжность"
Если 192 такта в SwapSet — это «слишком дорого», тогда давайте для честности все дружно откажемся и от прерываний.
не откажемся, потому что прерывания бывают нужны и сильно облегчают жизнь программисту, а рандомные мусорные вызовы - не нужны и не облегчают
Ведь там вообще неконтролируемые тысячи тактов уходят каждую секунду.
снова мимо, контролируемые и не каждую
Честно говоря думаю ниппер просто троллит, ну невозможно быть настолько тупым. Зачем тут тоже непонятно, площадка не та, возможно троллинг в телеграмчиках не вывозит. Непонятно только где доблестные бананаторы, перебанившие множество действительно увлеченных спектрумистов, иногда через чур эмоциональных, и не замечающих это. Простыни похожи на чатгпт, только нейросетка может написать что 40 тактов это 20 циклов лдира. Сама тема конечно забавная но никакого отношения к спектруму и к z80 не имеет, чисто стеб.
вот опять
про лдир тс оперативно удалил строчку из своего поста
- - - Добавлено - - -
Простыни похожи на чатгпт
чуть другой чат бот, но сути не меняет
Ради прикола попросил LeChat придумать применение моему методу. Потом просто передумал публиковать.
Это не страницка в соцюсетях, и не личный канал в телеге, где неугодные посты ты можешь удалить.
Это форум, раз начал тему, будь готов к критике.
Так что с желанием видеть только хвалебные посты от "поклонников" неэффективного метода это не сюда.
эта тема моя
А форум - наш! :)
Lethargeek
18.09.2025, 19:19
Честно говоря думаю ниппер просто троллит, ну невозможно быть настолько тупым. Зачем тут тоже непонятно, площадка не та,
или же платформа не та - полагаю, что столь нестандартно-сумрачный гений больше пригодился бы на БК :v2_dizzy_biggrin2:
null_device
18.09.2025, 21:34
Рабочий вариант - делал для AVR когда-то очень давно на ассемблере - работало как часики)))
Окромя вышеописанного жонглирования состоянием регистров - весь цимес, уложиться в одно прерывание для подпрограммы обработки im2.;)
Оттого, только три режима прерываний (два с половиной, по сути) и только один набор альтернативных регистров.
- - - Добавлено - - -
3. Разный контекст использования[/b]
• Прерывания — вынужденная плата за связь с железом. Все смирились: «так уж устроен Z80».
Срачи, по поводу производительности в количестве тактов, между прерываниями - пассы в пользу "сирых и убогих"? Так и запишем.
- - - Добавлено - - -
4. Цель SwapSet
SwapSet — не для tight loop и не для экономии каждого такта. Его задача:
• изолировать функции друг от друга;
• дать каждой задаче «свои регистры»;
• сделать код предсказуемым и модульным.
Вся беда , в том, что на данном этапе мы натыкаемся на зарождение многоядености для ЦП,и распараллеливания зачачь при написании программ в частности. Из-за чего, висли задачи в разных ядрах на первых порах многозадачности.
Dart Alver
19.09.2025, 00:24
2. Суммарный overhead
Параметр Прерывание SwapSet
Частота вызова 50 раз/сек (кадр) или 1000+ раз/сек (таймер) Несколько раз за программу
Суммарные траты 7 500 тактов/сек (50 Гц) или 150 000 тактов/сек (1 кГц) ~1000 тактов за всё время работы
Здесь всё очевидно: прерывания стабильно «съедают» тысячи тактов каждую секунду, SwapSet же — копейки за весь прогон программы.
Господи, ну что за глупости вы пишете!
Ну во первых Прерывание != сохранение+смена регистров. Обработка прерывания это цельный набор программ обслуживающий периферию и также могущий дополнительно выполнять какие-либо специфические задачи прописанные программистом. И оно запросто может утянуть и 2000 тактов и больше за 1 фрейм и это нормально (Может и меньше конечно, это что подвешено и для чего). Но суть то не в этом, а в том что сравнивать обработку прерывания с локальным сохранением регистров как минимум некорректно.
А во вторых - что значит несколько раз за программу ? Как это вообще согласуется с :
4. Цель SwapSet
SwapSet — не для tight loop и не для экономии каждого такта. Его задача:
• изолировать функции друг от друга;
• дать каждой задаче «свои регистры»;
• сделать код предсказуемым и модульным.
Цена в 192 такта оправдана, если цель — ясность и надёжность архитектуры.
Т.е. скажем в небольшой ассемблерной програмке из 30-40 процедур SwapSet будет использовать максимум штуки 3 и то может быть раза 2 и то врят-ли ? Тогда о какой изоляции, модульности, предсказуемости кода вообще может идти речь, если во всех остальных функциях будут теже самые попы и пуши ?
Чтобы ваша так сказать "цель" выполнялась, больше половины процедур должны задействовать swapset, иначе нет смысла огород вообще городить.
давайте, я не буду ничего доказывать вам. если вопрос в моем умении/неумении писать программы то вам не сюда. здесь мой ислледовательский проект. если есть что предложить, милости просим. а устраивать баттлы программистов, можете для этого создать свою отдельную тему.
Слишком много пафоса, вы сами пытаетесь доказать недоказуемое, а потом вопите что вас якобы троллят. Вот не надо никому ничего доказывать, не стройте из себя непризнанного гения, пишите реальные программы и всё само докажется, ну или не докажется но по крайней мере станет на свои места. ))
А про умение писать программы напомнило: "Чукча не читатель программист - чукча писатель идеолог программирования" ;)
Давай, в качестве доказательства работы твоего гениального метода, ты напишешь простейший "Hello, world" на асме, без использования функций из пзу.
Слишком много пафоса, вы сами пытаетесь доказать недоказуемое, а потом вопите что вас якобы троллят. Вот не надо никому ничего доказывать, не стройте из себя непризнанного гения, пишите реальные программы и всё само докажется, ну или не докажется но по крайней мере станет на свои места. ))
А про умение писать программы напомнило: "Чукча не читатель программист - чукча писатель идеолог программирования"
Наивная реализация Hello World
org 0x8000
ld hl,0x4000
ld (hl),l
ld de,0x4001
ld bc,0x7ff
ldir
//H
ld hl,0x4100
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x7e
inc h
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x42
//E
ld hl,0x4101
ld (hl),0x7E
inc h
ld (hl),0x40
inc h
ld (hl),0x7c
inc h
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x7e
//L
ld hl,0x4102
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x7e
//L
ld hl,0x4103
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x7e
//O
ld hl,0x4104
ld (hl),0x3c
inc h
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x3c
//W
ld hl,0x4106
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x5a
inc h
ld (hl),0x24
//O
ld hl,0x4107
ld (hl),0x3c
inc h
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x3c
//R
ld hl,0x4108
ld (hl),0x7c
inc h
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x7c
inc h
ld (hl),0x44
inc h
ld (hl),0x42
//L
ld hl,0x4109
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x40
inc h
ld (hl),0x7e
//D
ld hl,0x410a
ld (hl),0x78
inc h
ld (hl),0x44
inc h
ld (hl),0x42
inc h
ld (hl),0x42
inc h
ld (hl),0x44
inc h
ld (hl),0x78
//!
ld hl,0x410b
ld (hl),0x10
inc h
ld (hl),0x10
inc h
ld (hl),0x10
inc h
ld (hl),0x10
inc h
inc h
ld (hl),0x10
//
ld hl,0x400c
ld (hl),0x38
inc h
ld (hl),0x7c
inc h
ld (hl),0x54
inc h
ld (hl),0x7c
inc h
ld (hl),0x44
inc h
ld (hl),0x6c
inc h
ld (hl),0x7c
inc h
ld (hl),0x38
ret
Lethargeek
20.09.2025, 14:18
Да, это быстро, эффективно по тактам, но абсолютно нечитаемо. Поддерживать такой код, особенно если он будет большой, очень тяжело.
перевод: "не умею в макросы, и поэтому любой развёрнутый код нечитаем и тяжело поддерживается, адназначна!"
Не вижу смысла программировать в таком стиле.
а хто видит?
Компьютеры созданы для людей а не для процессоров.
для решения задач они созданы
умею в макросы
PROCEDURE MULBLK
BEGIN
LET R0 := BLKBEG
LET R2 := CAPTR
LET R3 := ACTCNT
THRU R3 ; blocks count
; INIT
LET R4 := (R2) ; block words count
IF RESULT IS NE THEN
LET R5 := 2(R2) ; first word command pointer
THRU R4
LET (R0)+ := (R5)+ ; copy next block
END
ELSE
LET (R0)+ := #NOP
END
IF APHASE NE #0 ; if not init calculating
; ACTION
LET R4 := 4(R2)
IF RESULT IS NE THEN
LET R5 := 6(R2) ; first word command pointer
THRU R4
LET (R0)+ := (R5)+ ; copy next block
END
END
END
END
LET (R0)+ := (PC) ; and return at end
RETURN
END MULBLK
Язык ассемблера + макросы. Транслируется стандартным макросассемблером PDP-11
ZXPIRATE
20.09.2025, 16:51
А где же твой гениальный метод swapset ? что, и без него может программа работать?
Just for those who are interested!
Bots (Haters), please do not disturb!!!
//// ExtSwapSet.asm
// v.2.0
// 9/21/2025
//
// Needs 8 bytes per structure:
//
// RegSet dw 0 //AF
// dw 0 //BC
// dw 0 //DE
// dw 0 //HL
// End
//
// The CurrentSet variable must be initialized
// prior to first use for the mechanism to function correctly.
//
InitSwapSet
ld ix,RegSet0+8
ld (CurrentSet),ix
ld ix,RegSet0
//in ix- register set address
SwitchContext
SwapSet di
ld (StoreSP),sp
//SaveSet
ld sp,(CurrentSet) // Current Set StackEnd
push hl
push de
push bc
push af
//LoadSet
ld sp,ix //Address New Set
pop af
pop bc
pop de
pop hl
ld (CurrentSet),sp
ld sp,(StoreSP)
ei
ret
//------------------------------------------------
SwapSet0 ld ix,RegSet0
jr SwapSet
SwapSet1 ld ix,RegSet1
jr SwapSet
SwapSet2 ld ix,RegSet2
jr SwapSet
SwapSet3 ld ix,RegSet3
jr SwapSet
//------------------------------------------------
//Setters:
PutA ld (ix+1),a
ret
PutBC ld (ix+2),c
ld (ix+3),b
ret
PutDE ld (ix+4),e
ld (ix+5),d
ret
PutHL ld (ix+6),l
ld (ix+7),h
ret
//---------------------------------------
MACRO Put_A
ld (ix+1),a
ENDM
MACRO Put_B
ld (ix+3),b
ENDM
MACRO Put_C
ld (ix+2),c
ENDM
MACRO Put_D
ld (ix+5),d
ENDM
MACRO Put_E
ld (ix+4),e
ENDM
MACRO Put_H
ld (ix+7),h
ENDM
MACRO Put_L
ld (ix+6),l
ENDM
MACRO Put_BC
ld (ix+3),b
ld (ix+2),c
ENDM
MACRO Put_DE
ld (ix+5),d
ld (ix+4),e
ENDM
MACRO Put_HL
ld (ix+7),h
ld (ix+6),l
ENDM
//---------------------------------------
//Getters:
GetA ld a,(ix+1)
ret
GetBC ld c,(ix+2)
ld b,(ix+3)
ret
GetDE ld e,(ix+4)
ld d,(ix+5)
ret
GetHL ld l,(ix+6)
ld h,(ix+7)
ret
//---------------------------------------
MACRO Get_A
ld a,(ix+1)
ENDM
MACRO Get_B
ld b,(ix+3)
ENDM
MACRO Get_C
ld c,(ix+2)
ENDM
MACRO Get_D
ld d,(ix+5)
ENDM
MACRO Get_E
ld e,(ix+4)
ENDM
MACRO Get_H
ld h,(ix+7)
ENDM
MACRO Get_L
ld l,(ix+6)
ENDM
MACRO Get_BC
ld b,(ix+3)
ld c,(ix+2)
ENDM
MACRO Get_DE
ld d,(ix+5)
ld e,(ix+4)
ENDM
MACRO Get_HL
ld h,(ix+7)
ld l,(ix+6)
ENDM
//---------------------------------------
Init
SaveSet ld (ix+0),0
ld (ix+1),a
ld (ix+2),c
ld (ix+3),b
ld (ix+4),e
ld (ix+5),d
ld (ix+6),l
ld (ix+7),h
ret
//---------------------------------------
Return
LoadSet di
ld (StoreSP),sp
ld sp,ix
pop af
pop bc
pop de
pop hl
ld sp,(StoreSP)
ei
ret
Return2
LoadSet2 ld a,(ix+1)
ld c,(ix+2)
ld b,(ix+3)
ld e,(ix+4)
ld d,(ix+5)
ld l,(ix+6)
ld h,(ix+7)
ret
StoreSP dw 0
CurrentSet dw RegSet0+8
//---------------------------------------
RegSet0 dw 0 //AF
dw 0 //BC
dw 0 //DE
dw 0 //HL
RegSet1 ds 8
RegSet2 ds 8
RegSet3 ds 8
org 0x8000
jp main
include "conio32.asm"
include "math.asm"
include "ExtSwapSet.asm"
fnt incbin "slim.fnt"
//Isolated putch:
IPutch push af
push bc
push de
push hl
call putch
pop hl
pop de
pop bc
pop af
ret
//---------------------------------------
main ld hl,fnt
call setfont
ld a,0x07
call setcolor
call clrscr
ld a,"A"
ld b,"B"
ld c,"C"
ld d,"D"
ld e,"E"
ld h,"H"
ld l,"L"
ld ix,TestSet1
call SaveSet
ld a,"0"
ld b,"1"
ld c,"2"
ld d,"3"
ld e,"4"
ld h,"5"
ld l,"6"
ld ix,TestSet2
call SaveSet
//Print Registers
//Classical solution:
//A
call IPutch
//B
ld a,b
call IPutch
//C
ld a,c
call IPutch
//D
ld a,d
call IPutch
//E
ld a,e
call IPutch
//H
ld a,h
call IPutch
//L
ld a,l
call IPutch
call putcr
//Print Registers
//SwapSet method:
ld ix,TestSet1
//A
call LoadSet
call putch
//B
call LoadSet
ld a,b
call putch
//C
call LoadSet
ld a,c
call putch
//D
call LoadSet
ld a,d
call putch
//E
call LoadSet
ld a,e
call putch
//H
call LoadSet
ld a,h
call putch
//L
call LoadSet
ld a,l
call putch
call putcr
ld ix,TestSet2
call LoadSet
//Print Registers
//Using Getters:
ld ix,TestSet1
//A
call GetA
call putch
//B
call GetBC
ld a,b
call putch
//C
call GetBC
ld a,c
call putch
//D
call GetDE
ld a,d
call putch
//E
call GetDE
ld a,e
call putch
//H
call GetHL
ld a,h
call putch
//L
call GetHL
ld a,l
call putch
call putcr
//Print Registers
//Using Macro for Getters:
ld ix,TestSet2
//A
Get_A
call putch
//B
Get_B
ld a,b
call putch
//C
Get_C
ld a,c
call putch
//D
Get_D
ld a,d
call putch
//E
Get_E
ld a,e
call putch
//H
Get_H
ld a,h
call putch
//L
Get_L
ld a,l
call putch
call putcr
ret
//Sets
TestSet1 ds 8
TestSet2 ds 8
org 0x8000
jp main
include "conio32.asm"
include "math.asm"
include "ExtSwapSet.asm"
fnt incbin "slim.fnt"
//---------------------------------------
main ld hl,fnt
call setfont
ld a,0x07
call setcolor
call clrscr
ld a,"H"
ld b,"E"
ld c,"L"
ld d,"L"
ld e,"O"
ld ix,TestSet1
call SaveSet
ld a,"W"
ld b,"O"
ld c,"R"
ld d,"L"
ld e,"D"
ld h,"!"
ld ix,TestSet2
call SaveSet
//Print Registers
//Using Macro for Getters:
ld ix,TestSet1
//A
Get_A
call putch
//B
Get_B
ld a,b
call putch
//C
Get_C
ld a,c
call putch
//D
Get_D
ld a,d
call putch
//E
Get_E
ld a,e
call putch
call putcr
ld ix,TestSet2
//A
Get_A
call putch
//B
Get_B
ld a,b
call putch
//C
Get_C
ld a,c
call putch
//D
Get_D
ld a,d
call putch
//E
Get_E
ld a,e
call putch
//H
Get_H
ld a,h
call putch
call putcr
ret
//Sets
TestSet1 ds 8
TestSet2 ds 8
))
Lethargeek
21.09.2025, 20:54
https://cdn2.bestdosgames.com/prod/uploads/2024/11/incredible_machine_5_5f32257990.png
; Универсальный макрос для вызова ЛЮБОЙ изолированной функции
MACRO CALL_ISOLATED function_name
push ix ; 1. Сохраняем указатель на текущий контекст
ld ix, function_name##_Set ; 2. Указываем на контекст функции
call SwitchContext ; 3. Переключаемся
; После возврата управления здесь
; IX автоматически восстановится при выходе из функции
ENDM
; Макрос для возврата из функции
MACRO RET_ISOLATED
pop ix ; Достаём указатель на контекст caller'а
jp SwitchContext ; Возвращаемся
ENDM
А форум - наш! :)
А ZX-Spectrum - Свободная платформа! :-D
Мы все здесь потому, что любим Spectrum за его свободу — так давайте не будем ограничивать друг друга в том, как мы эту свободу используем.
- - - Добавлено - - -
А где же твой гениальный метод swapset ? что, и без него может программа работать?
Конечно может. Но SwapSet(SwitchContext) имеет HumanityOverhead > 0. А в наивном коде HO == 0
Dart Alver
24.09.2025, 21:36
Мы все здесь потому, что любим Spectrum за его свободу — так давайте не будем ограничивать друг друга в том, как мы эту свободу используем.
И как же вас ограничивают ? Вам что кто-то запретил использовать ваш SwapSet чтоли ?
А то что на ваши посты практически нет положительных откликов, так уж извиняйте тут не хвалилка, отвечают то что думают о ваших рассуждениях и высказываниях - свобода-сЪ однако ! :v2_dizzy_surrender:
Конечно может. Но SwapSet(SwitchContext) имеет HumanityOverhead > 0. А в наивном коде HO == 0
Забавные вы батенька аргументы приводите.
"HumanityOverhead" - на запрос Яндекс отвечает "ничего не нашли", Google цинично отправляет на ваш же пост.
Онлайн переводчик от Яндекса выдаёт "Главенствующее положение человечества", от Гугла вообще не переводит, а если разорвать на слова, то "Humanity Overhead" - "Накладные расходы человечества" а "Humanity Over head" - "Человечество над головой". Думаю в данном случае Гугл правильно перевёл вторым случаем - "Накладные расходы человечества" > 0 :D
На самом деле не обманывайте ни себя, ни других. Да, конечно метод програмного обмена регистров использовать в ряде случаев может быть оправдано и удобно, но именно что особых где это действительно может быть оправдано, а не пихать его во все щели куда надо и не надо. Так же как использование других процедур и методов оправдано там где это необходимо и удобно.
Ваш SwapSet это самая обычная ассемблерная процедура не больше и не меньше. Улучшение читабельности программы он несёт не более чем и другие методы ассемблера, а иногда и менее. Оборачивание вызовов этого метода в макросы принципиально ничем не выделяется по сравнению с оборачиванием в макросы других методов и способов программирования на ассемблере.
Я не в курсе кто программирует ныне в наивном коде, и какое такое но==0, но если говорить о нативном коде, то думается цифровыми мнемониками уже давно практически никто не пользуется. :v2_wacko:
А утверждать о преимуществе SwapSet по сравнению с кодом ассемблера глупо, поскольку он сам такой же код ассемблера. Ассемблер он и есть ассемблер, хотите не ассемблерный метод - используйте языки более высокого уровня. А, макросы, да они могут повысить уровень ассемблера, если грамотно написаны, но повторюсь макросы можно писать для очень многих решений ассемблерного кода, а учитывая наличие в SjASMplus встроенного lua, теоретически можно даже сделать чтото вроде зачатка яву (хотя скорее ясу). Так что SwapSet тут преимуществ никаких не имеет.
"HumanityOverhead" - на запрос Яндекс отвечает "ничего не нашли", Google цинично отправляет на ваш же пост.
Онлайн переводчик от Яндекса выдаёт "Главенствующее положение человечества", от Гугла вообще не переводит, а если разорвать на слова, то "Humanity Overhead" - "Накладные расходы человечества" а "Humanity Over head" - "Человечество над головой". Думаю в данном случае Гугл правильно перевёл вторым случаем - "Накладные расходы человечества" > 0
HO, а точнее "Overhead On Humanity" — это не про "накладные расходы человечества", а про:
"Накладные расходы НА человечность" — плата за код, удобный для людей, а не для процессоров.
Я не в курсе кто программирует ныне в наивном коде, и какое такое но==0, но если говорить о нативном коде, то думается цифровыми мнемониками уже давно практически никто не пользуется.
речь именно о "наивной" прямолинейной реализации кода, понятной процессору и не перегруженной чем либо, чтобы быть понятнее программисту.
Да, конечно метод програмного обмена регистров использовать в ряде случаев может быть оправдано и удобно
BRAVO!
А утверждать о преимуществе SwapSet по сравнению с кодом ассемблера глупо, поскольку он сам такой же код ассемблера.
А я и не утверждал того что мой метод прыгнул выше головы, и о каких то его преимуществах перед обычным ассемблером. Да это ассемблер, но это еще и API для расширенных операций, отсутствующих в стандартной архитектуре z80.
Ваш SwapSet это самая обычная ассемблерная процедура не больше и не меньше.
Вы абсолютно правы — SwapSet действительно просто ещё один инструмент, а не панацея.
Я исследую, может ли такой подход упростить разработку больших проектов на Z80.
Если у вас есть идеи, как его улучшить или где он мог бы быть действительно полезен — буду благодарен за советы!
Спасибо за конструктивную критику!
Вот несколько сценариев применения, которые вижу я:
1. Изолированные функции
Каждая функция получает свой собственный контекст.
Это как «песочница» — туда кладём аргументы через сеттеры, переключаемся SwitchContext, выполняем код, возвращаемся, забираем результат через геттеры.
никаких push/pop, чистый вызов
функция не портит caller
2. Многозадачность
Если в сет добавить ещё PC и SP, он превращается в полноценный TCB (Task Control Block).
Тогда SwitchContext становится ядром планировщика.
можно реализовать кооперативную или вытесняющую многозадачность
любая задача живёт в своём сете
3. Объекты и структуры
Сет можно рассматривать как объект, где есть «поля» (ячейки под регистры) и «методы» (геттеры/сеттеры, макросы).
геттеры/сеттеры заменяют push/pop и позволяют адресовать регистры произвольно а не LIFO.
Тут необязательно даже переключаться в контекст. Можно работать с чужим объектом напрямую,
считывать и записывать значения другого сета.
Dart Alver
25.09.2025, 01:47
речь именно о "наивной" прямолинейной реализации кода, понятной процессору и не перегруженной чем либо, чтобы быть понятнее программисту.
Частенько перегруженная чем-либо "ненаивная" непрямолинейная реализация кода становится непонятна ни программисту, ни процессору. ))
А я и не утверждал того что мой метод прыгнул выше головы, и о каких то его преимуществах перед обычным ассемблером. Да это ассемблер, но это еще и API для расширенных операций, отсутствующих в стандартной архитектуре z80.
API для расширенных операций звучит конечно гордо, но по факту это будет обычный вызов подпрограммы либо встраивание куска кода через макрос. Необходимость менять весь сет регистров здесь прослеживается скорее почти никогда, да и подстановка push/pop макросом тоже вполне себе работает.
Я исследую, может ли такой подход упростить разработку больших проектов на Z80.
Если у вас есть идеи, как его улучшить или где он мог бы быть действительно полезен — буду благодарен за советы!
Ну х.з. единственное что приходит голову это стратегическая игра, где у каждого юнита имеется своё состояние, на которое он переключается при передаче управления. Но опять же далеко не факт, что переключение сета регистров окажется эффективней прямого чтения с памяти.
1. Изолированные функции
Каждая функция получает свой собственный контекст.
Это как «песочница» — туда кладём аргументы через сеттеры, переключаемся SwitchContext, выполняем код, возвращаемся, забираем результат через геттеры.
никаких push/pop, чистый вызов
функция не портит caller
Вытягивать все процедуры ассемблера в песочницу нерационально, падение производительности на вызовах колоссальное, плюс лишняя забивка памяти сетами и оболочками вызовов . Максимум несколько действительно нужных функций со своими наборами регистров, и даже в них будут появляться промежуточные результаты которые придётся либо кидать на стек, либо на ячейки памяти. Кроме того при работе с включенными прерываниями нужно следить чтоб свапинг не словил прерывание. Проще всего это делать, если вызывать свапинг после халта, либо использовать иные методы тайминга, например как Alone предлагал. А без прерываний... счас если что и пишется, то игрушки или изредка демки, где без AY совсем грустно ))
2. Многозадачность
Если в сет добавить ещё PC и SP, он превращается в полноценный TCB (Task Control Block).
Тогда SwitchContext становится ядром планировщика.
можно реализовать кооперативную или вытесняющую многозадачность
любая задача живёт в своём сете
Подвижки в принципе возможны, но тут первый вопрос а что многозадачить ? Когда на него отвечаем, то второй опять с обработкой прерываний. ))
3. Объекты и структуры
Сет можно рассматривать как объект, где есть «поля» (ячейки под регистры) и «методы» (геттеры/сеттеры, макросы).
геттеры/сеттеры заменяют push/pop и позволяют адресовать регистры произвольно а не LIFO.
Тут необязательно даже переключаться в контекст. Можно работать с чужим объектом напрямую,
считывать и записывать значения другого сета.
Правильнее сказать что адресация по структуре данных объекта через (ix+n) позволяет адресоваться к ячейкам памяти произвольно, а уж как работает с ними объект- загружает как сет регистров или вытягивает по одной как переменную (и далеко не факт что сетом регистров эффективнее будет), это вопрос уже конкретных условий реализации.
Lethargeek
25.09.2025, 02:26
может ли такой подход упростить разработку больших проектов на Z80.
даже самые большие на спектруме проекты не настолько большие, чтобы их размер стал проблемой
главные проблемы - ограниченная память и быстродействие, их "такой подход" лишь усугубляет
как его улучшить или где он мог бы быть действительно полезен
примерно нигде, ибо в принципе кодить на спек (да и на другие ретроплатформы) как индус - заведомо проигрышная стратегия
1. Изолированные функции
Каждая функция получает свой собственный контекст.
Это как «песочница» — туда кладём аргументы через сеттеры, переключаемся SwitchContext, выполняем код, возвращаемся, забираем результат через геттеры.
никаких push/pop, чистый вызов
функция не портит caller
нда, а ведь именно об этом была картинка :D
в таком случае, зачем же останавливаться на полпути?
геттеры, сеттеры - это недостаточно абстрактно и нестандартно!
пускай функции общаются пересылкой текстовых сообщений, чтоб всё по-взрослому!!
Описание имеющихся наработок:
Спецификация библиотеки SwapSet v2.0
Общее описание
Библиотека для управления изолированными контекстами регистров на Z80.
Каждый контекст представляет собой 8-байтовый блок памяти.
Структура контекста (8 байт)
Байт 0-1: AF (старший A, младший F)
Байт 2-3: BC
Байт 4-5: DE
Байт 6-7: HL
API ФУНКЦИИ
Инициализация системы
; Инициализирует систему SwapSet
; Использование: call InitSwapSet
InitSwapSet:
ld ix, RegSet0+8
ld (CurrentSet), ix
ld ix, RegSet0
jp SwapSet
Основное переключение контекста
; Переключает контекст регистров
; Вход: IX = адрес нового набора
; Использование: ld ix, NewSet \ call SwapSet
SwitchContext
SwapSet:
di
ld (StoreSP), sp
; Сохраняем текущий контекст
ld sp, (CurrentSet)
push hl
push de
push bc
push af
; Загружаем новый контекст
ld sp, ix
pop af
pop bc
pop de
pop hl
ld (CurrentSet), sp
ld sp, (StoreSP)
ei
ret
Быстрые обёртки для стандартных наборов
SwapSet0: ld ix, RegSet0 \ jr SwapSet
SwapSet1: ld ix, RegSet1 \ jr SwapSet
SwapSet2: ld ix, RegSet2 \ jr SwapSet
SwapSet3: ld ix, RegSet3 \ jr SwapSet
Геттеры (чтение из контекста)
Функции:
GetA: ld a, (ix+1) \ ret
GetBC: ld c, (ix+2) \ ld b, (ix+3) \ ret
GetDE: ld e, (ix+4) \ ld d, (ix+5) \ ret
GetHL: ld l, (ix+6) \ ld h, (ix+7) \ ret
Макросы:
MACRO Get_A: ld a, (ix+1) ENDM
MACRO Get_B: ld b, (ix+3) ENDM
MACRO Get_C: ld c, (ix+2) ENDM
MACRO Get_D: ld d, (ix+5) ENDM
MACRO Get_E: ld e, (ix+4) ENDM
MACRO Get_H: ld h, (ix+7) ENDM
MACRO Get_L: ld l, (ix+6) ENDM
MACRO Get_BC: ld b, (ix+3) \ ld c, (ix+2) ENDM
MACRO Get_DE: ld d, (ix+5) \ ld e, (ix+4) ENDM
MACRO Get_HL: ld h, (ix+7) \ ld l, (ix+6) ENDM
Сеттеры (запись в контекст)
Функции:
PutA: ld (ix+1), a \ ret
PutBC: ld (ix+2), c \ ld (ix+3), b \ ret
PutDE: ld (ix+4), e \ ld (ix+5), d \ ret
PutHL: ld (ix+6), l \ ld (ix+7), h \ ret
Макросы:
MACRO Put_A: ld (ix+1), a ENDM
MACRO Put_B: ld (ix+3), b ENDM
MACRO Put_C: ld (ix+2), c ENDM
MACRO Put_D: ld (ix+5), d ENDM
MACRO Put_E: ld (ix+4), e ENDM
MACRO Put_H: ld (ix+7), h ENDM
MACRO Put_L: ld (ix+6), l ENDM
MACRO Put_BC: ld (ix+3), b \ ld (ix+2), c ENDM
MACRO Put_DE: ld (ix+5), d \ ld (ix+4), e ENDM
MACRO Put_HL: ld (ix+7), h \ ld (ix+6), l ENDM
Управление контекстами
Сохранение текущих регистров:
SaveSet:
ld (ix+0), 0 ; F (упрощённо)
ld (ix+1), a
ld (ix+2), c
ld (ix+3), b
ld (ix+4), e
ld (ix+5), d
ld (ix+6), l
ld (ix+7), h
ret
Загрузка без переключения:
LoadSet2:
ld a, (ix+1)
ld c, (ix+2)
ld b, (ix+3)
ld e, (ix+4)
ld d, (ix+5)
ld l, (ix+6)
ld h, (ix+7)
ret
Архитектурные макросы
; Вызов функции в изолированном контексте
MACRO CALL_ISOLATED function_name
push ix
ld ix, function_name##_Set
call SwitchContext
ENDM
; Возврат из изолированной функции
MACRO RET_ISOLATED
pop ix
jp SwitchContext
ENDM
Системные переменные
StoreSP: dw 0
CurrentSet: dw RegSet0+8
Предопределённые контексты
RegSet0: ds 8
RegSet1: ds 8
RegSet2: ds 8
RegSet3: ds 8
Примеры использования
Изолированная функция:
Physics_Set: ds 8
ld ix, Physics_Set
call SwapSet
; ... код в изоляции ...
ld ix, Main_Set
call SwapSet
Работа с объектом без переключения:
ld ix, Enemy_Set
Get_A
Get_BC
Быстрое переключение:
call SwapSet0
call SwapSet1
call SwapSet2
Требования
Память: 8 байт на контекст + код библиотеки
Инициализация: обязательный вызов InitSwapSet
Прерывания: автоматически блокируются внутри SwapSet
Собственно библиотека
//// ExtSwapSet.asm
// v.2.0
// 9/21/2025
//
// Needs 8 bytes per structure:
//
// RegSet dw 0 //AF
// dw 0 //BC
// dw 0 //DE
// dw 0 //HL
// End
//
// The CurrentSet variable must be initialized
// prior to first use for the mechanism to function correctly.
//
InitSwapSet
ld ix,RegSet0+8
ld (CurrentSet),ix
ld ix,RegSet0
//in ix- register set address
SwitchContext
SwapSet di
ld (StoreSP),sp
//SaveSet
ld sp,(CurrentSet) // Current Set StackEnd
push hl
push de
push bc
push af
//LoadSet
ld sp,ix //Address New Set
pop af
pop bc
pop de
pop hl
ld (CurrentSet),sp
ld sp,(StoreSP)
ei
ret
//------------------------------------------------
SwapSet0 ld ix,RegSet0
jr SwapSet
SwapSet1 ld ix,RegSet1
jr SwapSet
SwapSet2 ld ix,RegSet2
jr SwapSet
SwapSet3 ld ix,RegSet3
jr SwapSet
//------------------------------------------------
//Setters:
PutA ld (ix+1),a
ret
PutBC ld (ix+2),c
ld (ix+3),b
ret
PutDE ld (ix+4),e
ld (ix+5),d
ret
PutHL ld (ix+6),l
ld (ix+7),h
ret
//---------------------------------------
MACRO Put_A
ld (ix+1),a
ENDM
MACRO Put_B
ld (ix+3),b
ENDM
MACRO Put_C
ld (ix+2),c
ENDM
MACRO Put_D
ld (ix+5),d
ENDM
MACRO Put_E
ld (ix+4),e
ENDM
MACRO Put_H
ld (ix+7),h
ENDM
MACRO Put_L
ld (ix+6),l
ENDM
MACRO Put_BC
ld (ix+3),b
ld (ix+2),c
ENDM
MACRO Put_DE
ld (ix+5),d
ld (ix+4),e
ENDM
MACRO Put_HL
ld (ix+7),h
ld (ix+6),l
ENDM
//---------------------------------------
//Getters:
GetA ld a,(ix+1)
ret
GetBC ld c,(ix+2)
ld b,(ix+3)
ret
GetDE ld e,(ix+4)
ld d,(ix+5)
ret
GetHL ld l,(ix+6)
ld h,(ix+7)
ret
//---------------------------------------
MACRO Get_A
ld a,(ix+1)
ENDM
MACRO Get_B
ld b,(ix+3)
ENDM
MACRO Get_C
ld c,(ix+2)
ENDM
MACRO Get_D
ld d,(ix+5)
ENDM
MACRO Get_E
ld e,(ix+4)
ENDM
MACRO Get_H
ld h,(ix+7)
ENDM
MACRO Get_L
ld l,(ix+6)
ENDM
MACRO Get_BC
ld b,(ix+3)
ld c,(ix+2)
ENDM
MACRO Get_DE
ld d,(ix+5)
ld e,(ix+4)
ENDM
MACRO Get_HL
ld h,(ix+7)
ld l,(ix+6)
ENDM
//---------------------------------------
Init
SaveSet ld (ix+0),0
ld (ix+1),a
ld (ix+2),c
ld (ix+3),b
ld (ix+4),e
ld (ix+5),d
ld (ix+6),l
ld (ix+7),h
ret
//---------------------------------------
Return
LoadSet di
ld (StoreSP),sp
ld sp,ix
pop af
pop bc
pop de
pop hl
ld sp,(StoreSP)
ei
ret
Return2
LoadSet2 ld a,(ix+1)
ld c,(ix+2)
ld b,(ix+3)
ld e,(ix+4)
ld d,(ix+5)
ld l,(ix+6)
ld h,(ix+7)
ret
StoreSP dw 0
CurrentSet dw RegSet0+8
//---------------------------------------
RegSet0 dw 0 //AF
dw 0 //BC
dw 0 //DE
dw 0 //HL
RegSet1 ds 8
RegSet2 ds 8
RegSet3 ds 8
Примечание
Алиасы:
SwapSet == SwitchContext
SaveSet == Init
LoadSet == Return
Powered by vBulletin® Version 4.2.5 Copyright © 2026 vBulletin Solutions, Inc. All rights reserved. Перевод: zCarot