петък, 31 май 2013 г.

SBCL за ARM част I

Ами да започваме...

Напълно в стила на метакръговите оценители, SBCL се билдва от...SBCL (е, може и от друг Lisp и му трябва и малко GCC, но като цяло е написан на Lisp). Това не е нещо ново -- и GCC е написан на C и му трябва малко помощ да бутстрапне.

По принцип си има билд скрипт, който ако вече има някакъв Lisp и GCC на машината -- работи. В случая обаче искаме да правим крос-компилация, т.е. скрипта няма да ни свърши работа наготово.

Билд скрипта общо взето прави следното (плюс някакви дреболии):

make-configh.sh
make-host-1.sh
make-target-1.sh
make-host-2.sh
make-target-2.sh

Тези, които имат host в тях, се пускат на хоста за крос-компилация, т.е. на PC-то ми. Другите, които имат target в тях, се пускат на Малинката. config-а го пускам също на Малинката.

make-config.sh се опитва да разбере върху каква система работи и да установи правилните параметри (нещо като ./configure)

make-host-1.sh създава лисп частта на една орязана версия на SBCL, написана изцяло на portable ASNI Common Lisp, така че да може да се компилира от какъвто Lisp има на хоста. Въпреки, че пише орязана, тя съдържа целия SBCL крос-компилатор, способен да върви на host машината, но да генерира код за ARM. Друго важно нещо, което make-host-1.sh прави, е че създава C .h файлове, необходими за да се билдне C частта на бъдещия SBCL (т.н. genesis).

make-target-1.sh прави C частта (т.н. runtime) на бъдещия SBCL. Тя се пуска на Малинката и се компилира с нейния GCC, генерирайки нормален ARM изпълним файл. От така получения ELF binary се извличат символите и адресите (посредством nm) на всички функции и техните адреси, които записани в sbcl.nm се копират на host машината, заедно с разни неща от .h файловете. Те ще трябват в следващата стъпка, в който крос-компилатора компилира окончателната версия на лисп частта и трябва да знае адресите на функциите без те да са всъщност в паметта.

make-host-2.sh се пуска отново на PC-то.  Тук крос-компилатора, компилиран в първата стъпка се използва, за да компилира (този път за ARM) друга орязана версия на SBCL, която съдържа почти всичко (без някои по-специфични неща, механизъма за които още не е компилиран :–)). Готовите неща се записват в т.н. cold core, който е лисп частта, предназначена за последната стъпка.

make-target-2.sh се пуска на Малинката. Това е компилирания в make-target-1.sh runtime, който прочита създадения в make-host-2.sh cold core и му предава управлението. Там вече има native ARM компилатор, с който се докомпилират всички останали неща и се създава окончателния sbcl.core със всичките му салтанати.


make-host-1.sh, заедно с make-target-1.sh са като че ли единствените неща, които трябва да се пипнат. Доколкото успявам да схвана, останалите работи са на машинно-независим Lisp, така че те би трябвало да минат лесо. make-host-1.sh обаче е яката работа.


И така: да почваме по ред. Първите промени са тук:


https://github.com/vpavlov/sbcl/commit/4e21ee17c471aa653541d777974646c8beb0d91a


Файл по файл, промените са следните:

make-config.sh се опитва да разбере върху каква система работи и да установи правилните параметри (нещо като ./configure). Той обаче работи само за платформи, за които вече може да се компилира, така че трябва малко да го пипнем, за да добавим и ARM:
  • от uname -n взимаме архитектурата, която докладва Малинката и виждаме, че е arm6l, добавяме в необходимия case;
  • понеже ОС-а си е Linux, си мисля, че ще мога да добавя и тредове и футекси (каквото и да значи това), затова добавям :sb-thread и :sb-futex към local-target-features.lisp-expr (ltf, малко повече за това -- след малко);
  • съответно добавяме case за вече познатата по-горе архитектура, в който се установяват другите неща от ltf. Тук копирам всичко от x86 архитектурата, пък после ще му мислим, като му дойде времето.
Малко за local-target-features.lisp-expr, съкратено на ltf. Съдържа конфигурацията на новонаправения компилатор. Вместо някакви обскюрнати конфигурационни файлове, това си е lisp списък с keywords (без значение конкретиката, просто имена, започващи с двоеточие), наличието на които задейства някакви фийчъри. По-нататък кода си го прочита този списък  и си прави каквото трябва. За момента е важно да се отбележи, че в ltf make-config.sh автоматично слага :архитектурата, така че там ще има :arm. Останалите, както казах, съм ги взел от x86 порта и ще ги мислим по-нататък.

src/cold/shared.lisp -- промените тук са предимно свързани с едни sanity check–ове, които проверяват дали написаните в ltf фийчъри са съвместими едни с други. Така например проверява за кои платформи се поддържат thread–ове, и ако не сме в тях -- се оплаква. Е, тъй като въвеждаме нова платформа, се налага да променим малко въпросните sanity check-ове.

Другата по-важна промяна е във функцията stem-remap-target (на ред 257). Ето и цялата функция:


(defun stem-remap-target (stem)
  (let ((position (search "/target/" stem)))
    (if position
      (concatenate 'string
                   (subseq stem 0 (1+ position))
                   #!+x86 "x86"
                   #!+x86-64 "x86-64"
                   #!+sparc "sparc"
                   #!+ppc "ppc"
                   #!+mips "mips"
                   #!+alpha "alpha"
                   #!+hppa "hppa"
                   #!+arm "arm"
                   (subseq stem (+ position 7)))
      stem)))


Малко пояснение: сорсовете, които трябва да се билднат (един вид -- проекта), се намират също в лисп файл (build-order.lisp-expr в основната директория). В него на места се срещат пътища от сорта на src/compiler/target/inst.lisp Горната функция се грижи за това стринга /target/ в съответните файлови имена да се замени със стринг, който да отговаря на платформата (в нашия случай напр. src/compiler/arm/inst.lisp) По този начин се компилират файловете само за съответната платформа. Е, тук се налага да добавим случая за arm, който, разбираемо липсваше.

Малко странно изглеждат редовете с #! отпред. Това е conditional read, нещо като #ifdef ... #endif, но по-хитро. Нещото, което е след #! е keyword от ltf. Някъде в началото, преди за пръв път да се използва #!, се дефинира т.н. reader macro. Това е лисп код, който се изпълява по време на парсването на лисп сорсовете. Задейства се от прочитане на определена последователност, в случая #!+ (или #!-). Функцията проверява дали следващата keyword е налична в списъка, прочетен от ltf и ако е наличен -- прочита следващата форма; ако не е наличен -- я пропуска. По този начин се получава условна компилация в зависимост от нещата, описани в ltf. Както можете да се сетите, кода по-нататък е изпъстрен от такива #!+ неща

Една скоба: съгласно стандарта има макроси #+ и #-, които правят същото, но те работят с точно определен списък с feature-и (наречен... :features), който го има в host компилатора, но понеже не искаме да се бъзикаме с него и да му омешваме нашите фийчъри с неговите, се налага въвеждането на още един набор reader макроси, този с #!+ и #!-

Толкова за днес. Следват изцяло платформено зависими неща (huuuuge) и за първото от тях -- дефиницията на регистрите и т.н. -- в отделен пост, че темата е голяма...

Няма коментари: