glibc on sparc
Давно ничего не писал. Недавно решил дополнить таблицу бинарников для ghc-6.12.3. Здесь (табличка внизу) не хватало ppc и sparc.
С ppc всё было весело и без моего участия. Joseph быстро собрал бинарник и даже убедился, что libffi сломан и там, а mounty собрал и убедился, что патч помогает. Спасибо им!
Со sparc проблем особых почти не возникло. Почти!
Я выпросил доступ к одной из niagara. У этого монстра 32 процессора и 32 гига ОЗУ.
$ cat /proc/cpuinfo
cpu : UltraSparc T1 (Niagara)
fpu : UltraSparc T1 integrated FPU
pmu : niagara
prom : OBP 4.25.0 2006/11/07 23:24
type : sun4v
ncpus probed : 32
ncpus active : 32
D$ parity tl1 : 0
I$ parity tl1 : 0
Cpu0ClkTck : 0000000047868c00
Cpu1ClkTck : 0000000047868c00
Cpu2ClkTck : 0000000047868c00
Cpu3ClkTck : 0000000047868c00
.. (еще 28 таких строк)
Cpu30ClkTck : 0000000047868c00
Cpu31ClkTck : 0000000047868c00
MMU Type : Hypervisor (sun4v)
State:
CPU0: online
CPU1: online
CPU2: online
CPU3: online
... (ну вы поняли :])
CPU30: online
CPU31: online
Так выглядит htop:
Частота одного процессора:
Calibrating delay using timer specific routine.. 2415.04 BogoMIPS (lpj=1207520)
У меня на amd64 буке почти столько же. Значит под 1.6GHz. make там запускается с параметром -j33 (33 параллельных процесса сборки). 32 гигогерцовых головы - это много, но гадкая система сборки ghc не тестировалась на числе голов больше, чем 8 и оказалась сломанной на niagara (починено в ghc-HEAD). В итоге я собирал ghc-6.12.3 400 минут вместо 12.5 (шучу, там не настолько всё параллельно, но близко к тому).
Это не оказалось проблемой и всё собралось с первого раза (ну почти, старый бинарник ghc требовал gcc версии 4.1; ghc-6.10+ больше не полагается на кодогенерацию gcc и генерит код сам. ghc-6.12.3 способен собрать себя за 240 минут на том же железе).
После этого я решил побаловаться на niagara и попробовал обновить мир до ~sparc с FEATURES=test, которые запускает тесты(нде они есть). Openssl, например, проверяет, что все ее криптоалгоритмы генерят нужные данные. Все дела происходят в chroot, так что ни одна production система не пострадала. Тут же полезли страшные вещи: некоторые тесты стали зависать в самых неожиданных местах (например, тесты gdb), хотя должны были завершиться по таймауту в 300 секунд (взможно, это баг в ptrace).
Я решил не заморачиваться и просто попробовал обновить систему - без включенных FEATURES=test. И хрен там - не собрался glibc-2.12.1: репорт. Казалось бы - делов то, наверное, сломал компилятор или еще что-то. Попросил других sparc’овцев проверить - та же картина: glibc-2.11.2 работает, а glibc-2.12.1 - нет (падает всё на том-же rpcgen).
Быстро баг никто не пофиксил и я решил немного поисследовать, как там жизнь на sparc’е. Сам rpcgen - это простая программа, которая парсит SUN RPC спек файлы и генерит RPC заглушки на C (прога изменялась в 2005 году). Она используется для сборки всяких юзерспейсных NFS серверов, клиентов и прочих штук, которые регистрируют свою процедуры в службе portmapper (та, которую все вырезают, когда ставят debian ;]).
Как видно в баге я начал подозревать всё и вся, но потом вырисовалась ужасная картина: на sparc сломана функция memcpy! Функция просто копирует байты из одной области памяти в другую. Области не должны пересекаться - всё просто. memcpy - одна из функций, которая серьезно менялась для sparc между релизами glibc.
Казалось бы - чего проще скопировать 36 байт (выровненных по какой надо границе, все дела) со стека в динамическую память. А вот хер там. sparc - big endian железяка, не поддерживающая невыровненный доступ к памяти:
$ cat align_test.c
#include <stdio.h>
int main() {
char buf[] = { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9 };
printf ("+0: %08X\n", *(int *)(buf + 0));
printf ("+1: %08X\n", *(int *)(buf + 1));
printf ("+2: %08X\n", *(int *)(buf + 2));
printf ("+3: %08X\n", *(int *)(buf + 3));
printf ("+4: %08X\n", *(int *)(buf + 4));
return 0;
}
$ ./a
+0: 01020304
Bus error
По сему функция memcpy должна быть сложнее, чем на amd64 (а значит сделать там ошибку легче).
Посмотрим на исходники:
- i386: всё гениальное - просто. Практически одна инструкция: rep movsb (ну и флаг направления наращивания регистров смещения соблюден)
- amd64: чуть грустнее, но базовая операция такая-же плюс копирование больших выровненных блоков делается параллельно (регистров много - можно веселиться)
- sparc: и судя по всему в ядре оно работает, а в glibc похожая портянка - нет :]
Надо курить доки. Всяких ссылок и спеков навалом. Немного ABI для чайников типа меня.
Сначала мне было непонятно, почему одна голова sparc работает в разы медленнее, чем мой core2 примерно той же частоты (сильно чувствуется на ./configure). Теперь понемногу проясняется: sparc требует сравнительно дофига ассемблерного кода для вызова C функции (и вынужден сохранять регистровые фреймы в память, когда делает системные вызовы).