SDCC Z80 и как с ним бороться
Драма в трёх актах с разрушенными судьбами программ и убитыми человеко-часами.
Сразу должен извиниться перед авторами SDCC за всё хорошее, что я напишу далее. С одной стороны, я должен поблагодарить создателей за их труд, особенно, в отсутствие разумных альтернатив. С другой — благодарить авторов за их кривое поделие я не могу ни искренне, ни неискренне — вообще никак.
Акт первый. Решимость.
За поиски C компилятора для Z80 меня вынудило взяться осознание того факта, что I'm too old for this shit писать большие объемы кода на ассемблере. То есть, либо я пишу на си, либо я ничего не пишу. Относясь к почтением к мастерам демосцены и не относя себя к оным, я не собирался вышивать крестиком на асме, а хотелось делатьБолее или менее полный список компиляторов языка С для непроцессора состоит из:
— Hitech C. Не поддерживает ничего кроме 99. Не обновляется и обновляться не будет. Труп.
— IAR Z80. Аналогично предыдущему. Имеет феерический набор смертельных глюков, не подлежащих исправлению. Лебединое озеро.
— z88dk. Генерит очень странный код и выглядит так же. Пощупал свежую версию, и оказалось, что теперь они перешли на
— SDCC.
Речь пойдёт о свежей на момент написания текста версии сабжа 3.6 и только в версии Z80. Слава небесам, мне не довелось пробовать версии для других архитектур.
Акт второй. Осознание.
Из плюсов данного компилятора можно назвать, как говорят в таких случаях, «спасибо, что живой». Ну какбэ есть коммьюнити, выходят релизы, оперативно правят баги, отвечают на вопросы. Но создаётся впечатление, что процесс приносит авторам удовлетворение самим собой, так как результатов его я не ощущаю, например.Всё остальное, что я увидел в SDCC Z80, представляет собой список минусов.
Вот его краткий и неполный вариант:
— медленная компиляция, сравнимая по скорости с рендерингом 3D Max на старых компах,
— полное отсутствие оптимизаций типа: удаление неиспользуемых функций, инлайн редко используемых функций, игнор пустых функций и тд.,
— оптимизация кода
— передача абсолютно всех входных аргументов в функцию через стек, независимо от их количества, вместо передачи через регистры (на самом деле нет: есть calling convention __z88dk_fastcall, который передает аргументы через DEHL, но (сапрайз!) позволяет передать в функцию только один аргумент),
— странная любовь к регистровой паре IX, где надо и где не надо, что не добавляет ни скорости, ни компактности коду,
— странноватый асм: перед константами надо ставить #,
— неумение последовательно располагать в памяти код секций, заданных по одному адресу, вместо этого они будут лежать поверх друг друга.
Акт третий. Борьба.
Конкретные проблемы, с которыми я столкнулся и которые пришлось решать.Стартап код crt0.s линкуется крайне удивительным образом, если делать это при помощи опции --no-std-crt0.
В этом случае часть либных функций (например, «длинное» умножение и деление) попадает не в секцию CODE, а в некий небанкуемый HOME, который вообще находится по другому адресу, который (см.выше) нельзя расположить последовательно с CODE.
Решение, которое я нашел — не использовать --no-std-crt0, а вместо этого запихнуть свой стартап вместо штатного
По поводу выкидывания dead code я аж обратился в сапорт.
Весь код, который находится в компилируемом сорце попадает в бинарь безоговорочно.
Весь код, который находится в линкуемом модуле .rel попадает в бинарь безоговорочно.
Весь код, который находится в либе .lib в одном модуле (попавшем в либу из одного .rel) попадает в бинарь безоговорочно.
И только если нафаршировать либу таким образом, чтоб каждая функция лежала в отдельном модуле, в бинарь попадут только те функции, к которым происходит обращение.
Для этого пришлось написать тулзу, которая разбивает исходник на С на много мелких файлов, каждый из которых содержит одну функцию или массив констант, обрамлённый скобками { }.
В каждом файле дублируются #include "...", если таковые были задетекчены по ходу сорца.
Тулза тупая, под стать SDCC, синтаксис толком не понимает, а парсит только по отдельным признакам. Однако, помогает решить проблему с выбрасыванием неиспользуемого кода.
Пример батника для создания либы.
sdcc-lib-split.exe ft812dl.c >>lib.lst
sdcc-lib-split.exe ft812func.c >>lib.lst
sdcc-lib-split.exe ft812math.c >>lib.lst
for /f %%i in (lib.lst) do (
echo %%i
sdcc -mz80 --std-sdcc11 --opt-code-speed -c %%i -o %%i.rel
sdcclib ft812.lib %%i.rel
)
Конечно, документацию читают только слабаки, но после поиска хоть какой-нибуть инфы в доке по проблеме --no-std-crt0 я понял, что в данном случае документация в полной мере отражает всё остальное.
Такие дела.
2 комментария
Причина по которой я испугался, была такой же — постоянные манипуляции с регистром IX, когда надо, и когда не надо.
Отсюда вывод, пилить надо свое. По словам z80 gcc patch в groups.google.com отыскивается что-то интересное, к сожалению, дальше я не продвинулся.
Также, на github'е валяются всевозможные форки LLVM в разной степени допиленности.