glibc on sparc

September 25, 2010

Давно ничего не писал. Недавно решил дополнить таблицу бинарников для 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 (а значит сделать там ошибку легче).

Посмотрим на исходники:

Надо курить доки. Всяких ссылок и спеков навалом. Немного ABI для чайников типа меня.

Сначала мне было непонятно, почему одна голова sparc работает в разы медленнее, чем мой core2 примерно той же частоты (сильно чувствуется на ./configure). Теперь понемногу проясняется: sparc требует сравнительно дофига ассемблерного кода для вызова C функции (и вынужден сохранять регистровые фреймы в память, когда делает системные вызовы).