Старый GLib vs новый Clang


Ковыряясь в разнообразном СПО, я периодически нахожу всякие интересные штуки: иногда это просто смешной комментарий, иногда — нечто остроумное в более широком смысле. Подобные подборки периодически появляются и в «глобальном Интернете», и на Хабре — есть, скажем, широко известный вопрос на StackOverflow про комментарии в коде, а здесь недавно публиковалась подборка забавных названий юрлиц и топонимов. Попробую и я структурировать и выложить то, что постепенно у меня копилось. Под катом вас ждут цитаты из QEMU, ядра Linux, и не только.


Linux kernel


Думаю, для многих не является секретом то, что письма из Linux Kernel Mailing List периодически расходятся на цитаты. Поэтому давайте лучше посмотрим в код. И сразу же система сборки ядра встречает нас сюрпризом: как известно, проекты, собираемые Autoconf имеют Makefile с двумя стандартными целями для очистки: clean и distclean. Естественно, ядро не собирается с помощью Autoconf, да и чего только стоит один лишь menuconfig, поэтому целей здесь побольше: clean, distclean и mrproper — да-да, «Мистер Пропер», ядро чище в два раза быстрей.


Кстати о системе конфигурирования: когда-то давно я был удивлён, когда наткнулся в ней помимо понятных команд вроде allnoconfig, allyesconfig (подозреваю, что вкомпилироваться может что-нибудь сильно отладочное, поэтому сейчас бы я такое загружать на реальном железе не рискнул бы...) и allmodconfig на загадочную цель allrandconfig. «Они что, издеваются» — подумал я, потом рассказал об этом наблюдении своему знакомому, на что тот ответил, что, вероятно, это вполне осмысленная команда, но не для реальной сборки, а для тестирования правильности расстановки зависимостей между опциями — как я сказал бы уже сейчас, этакий фаззинг параметров конфигурации.


Впрочем, есть в ядре жизнь и за пределами сборочной системы: документация иногда представляет не только техническую, но и, своего рода, художественную ценность. Предположим, вы хотите предупредить пользователей спящего режима о его хрупкости и риске потери данных при несоблюдении определённых правил. Я бы уныло написал, мол ВНИМАНИЕ: <подставить пару максимально нудных строчек>. Но разработчик, писавший это, поступил по-другому:


Some warnings, first.

 * BIG FAT WARNING *********************************************************
 *
 * If you touch anything on disk between suspend and resume...
 *              ...kiss your data goodbye.
 *
 * If you do resume from initrd after your filesystems are mounted...
 *              ...bye bye root partition.
 *          [this is actually same case as above]
 *
 * ...

Маленькие хитрости


Неудивительно, что не всякий код можно собирать с оптимизациями: когда я попытался принудительно их включить для всех объектных файлов, я закономерно напоролся на некий источник энтропии или что-то подобное, который выдавал #error, если включена оптимизация. Ну, криптография — она такая. А не хотите ли код, который не соберётся, если вы отключите все оптимизации, инлайнинг и т.д.? Как такое возможно? А это такой статический assert:


/* SPDX-License-Identifier: GPL-2.0 */

// ...

/*
 * This function doesn't exist, so you'll get a linker error if
 * something tries to do an invalidly-sized xchg().
 */
extern void __xchg_called_with_bad_pointer(void);

static inline
unsigned long __xchg(unsigned long x, volatile void *ptr, int size)
{
        unsigned long ret, flags;

        switch (size) {
        case 1:
#ifdef __xchg_u8
                return __xchg_u8(x, ptr);
#else
                local_irq_save(flags);
                ret = *(volatile u8 *)ptr;
                *(volatile u8 *)ptr = x;
                local_irq_restore(flags);
                return ret;
#endif /* __xchg_u8 */

// ...

        default:
                __xchg_called_with_bad_pointer();
                return x;
        }
}

Предполагается, видимо, что при любом использовании с константным аргументом эта функция развернётся в одну лишь ветвь switch, а при использовании с корректным аргументом, эта ветвь будет не default:.
В не оптимизированном виде эта функция вызовет ошибку линковки практически by design...


Знаете ли вы


  • … что в ядре есть JIT-компилятор байткода из пользовательского режима? Эта технология называется eBPF и используется для маршрутизации, трассировки и много чего ещё. Кстати, если не боитесь экспериментальных «ядерных» инструментов, посмотрите на пакет bpftools.
  • … что в ядро можно уйти минут на пять процессорного времени? Есть такой системный вызов sendfile, копирующий байты из одного файлового дескриптора в другой. Если указать ему один и тот же дескриптор и выставить правильное смещение в файле, он будет наматывать одни и те же данные, пока не скопирует 2 Гб.
  • … что есть вариант работы гибернации, проводимой пользовательским процессом — не удивлюсь, если можно и на сетевое хранилище так сохраниться.

QEMU


Вообще, когда я почитал Роберта Лава про устройство Linux kernel, а потом полез в исходники QEMU, у меня возникло некоторое ощущение дежавю. Там были и списки, встраивающиеся в структуры по значению (а не как в начальном курсе программирования учат — через указатели), и некая подсистема RCU (что это такое, я так до конца и не понял, но в ядре оно тоже есть) и, наверное, много ещё похожего.


С чем первым делом знакомится аккуратный человек, желающий поработать над проектом? Наверное, с Coding style. И уже в этом, можно сказать, парадном, документе, мы видим:


1. Whitespace

Of course, the most important aspect in any coding style is whitespace.
Crusty old coders who have trouble spotting the glasses on their noses
can tell the difference between a tab and eight spaces from a distance
of approximately fifteen parsecs.  Many a flamewar has been fought and
lost on this issue.

Здесь же про извечный вопрос о максимальной длине строк:


Lines should be 80 characters; try not to make them longer.
...
Rationale:
 - Some people like to tile their 24" screens with a 6x4 matrix of 80x24
   xterms and use vi in all of them.  The best way to punish them is to
   let them keep doing it.
...

(Хмм… Это же в два раза больше по каждой оси, чем иногда использую я. Это такой Linux HD?)


Там ещё много интересного — почитайте.


И снова хитрости


Говорят, С — низкоуровневый язык. Но если хорошо извратиться, проявлять чудеса compile-time кодогенерации можно и без всяких Scala и даже C++.


Вот, например, в кодовой базе QEMU притаился файл softmmu_template.h. Когда я увидел это название, то подумал, что его предполагается скопировать в свою реализацию TCG backend-а и подправлять, пока из него не получится правильная реализация TLB. Как бы не так! Вот как правильно его использовать:


accel/tcg/cputlb.h:


define DATA_SIZE 1
#include "softmmu_template.h"

#define DATA_SIZE 2
#include "softmmu_template.h"

#define DATA_SIZE 4
#include "softmmu_template.h"

#define DATA_SIZE 8
#include "softmmu_template.h"

Как видите, ловкость рук и никакого C++. Но это довольно простой пример. Как насчёт чего посложнее?


Есть такой файл: tcg/tcg-opc.h. Содержимое его довольно загадочно и выглядит как-то так:


...
DEF(mov_i32, 1, 1, 0, TCG_OPF_NOT_PRESENT)
DEF(movi_i32, 1, 0, 1, TCG_OPF_NOT_PRESENT)
DEF(setcond_i32, 1, 2, 1, 0)
DEF(movcond_i32, 1, 4, 1, IMPL(TCG_TARGET_HAS_movcond_i32))
/* load/store */
DEF(ld8u_i32, 1, 1, 1, 0)
DEF(ld8s_i32, 1, 1, 1, 0)
DEF(ld16u_i32, 1, 1, 1, 0)
DEF(ld16s_i32, 1, 1, 1, 0)
...

На самом деле всё очень просто — используется он вот так:


tcg/tcg.h:


typedef enum TCGOpcode {
#define DEF(name, oargs, iargs, cargs, flags) INDEX_op_ ## name,
#include "tcg-opc.h"
#undef DEF
    NB_OPS,
} TCGOpcode;

Или так:


tcg/tcg-common.c:


TCGOpDef tcg_op_defs[] = {
#define DEF(s, oargs, iargs, cargs, flags)          { #s, oargs, iargs, cargs, iargs + oargs + cargs, flags },
#include "tcg-opc.h"
#undef DEF
};

Даже странно, что с ходу других случаев использования не нашлось. И заметьте, в данном случае нет никаких хитрых скриптов для кодогенерации — только C, только хардкор.


Знаете ли вы


  • … что QEMU может работать не только в режиме эмуляции полной системы, но и запускать отдельный процесс для другой архитектуры, общающийся с хостовым ядром?

Java, JVM и все-все-все


Что же я всё о Линуксе? Давайте о чём-нибудь кросс-платформенном поговорим. О JVM, например. Ну, про GraalVM, наверное, многие разработчики в этой экосистеме уже слышали. Если не слышали, то в двух словах: это эпично. Итак, после рассказа о Graal перейдём к старой-доброй JVM.


Иногда JVM нужно остановить все managed-потоки — стадия сборки мусора такая заковыристая или ещё чего — но вот незадача, останавливать потоки можно только на так называемых safepoints. Как рассказывается здесь, нормальная проверка глобальной переменной занимает много времени, в том числе некое шаманство с memory barriers. Что же сделали разработчики? Они ограничились одним чтением переменной.


Почти как в HQ9+

Есть такой шуточный язык — HQ9+. Он создавался как "очень удобный учебный язык программирования", а именно, на нём очень просто выполнять типичные задачи, которые задают ученикам:


  • по команде 'H' интерпретатор печатает Hello, World!
  • по команде 'Q' печатает сам текст программы (квайн)
  • на '9' он печатает текст песенки про 99 bottles of the beer
  • по 'i' он увеличивает переменную i на единицу
  • больше он ничего делать не умеет, а зачем?..

Как же JVM одной инструкцией добивается цели? А очень просто — при необходимости остановки она убирает отображение для страницы памяти с этой переменной — потоки падают по SIGSEGV, а JVM их паркует и снимает с паузы, когда "техобслуживание" заканчивается. Помню, на StackOverflow на вопрос с собеседования How do you crash a JVM? ответили:


JNI. In fact, with JNI, crashing is the default mode of operation. You have to work extra hard to get it not to crash.

Шутки шутками, а иногда в JVM оно на самом деле так.


Ну и раз уж я упомянул про кодогенерацию в Scala, да и говорим мы сейчас как раз об этой экосистеме, то вот вам занятный факт: кодогенерация в Scala (та, которая макросы), устроена приблизительно так: вы пишите код на Scala, использующий API компилятора, и компилируете его. Потом при следующем запуске компилятора вы просто передаёте получившийся кодогенератор в classpath самого компилятора, а тот, увидев специальную директиву, его вызывает, передав синтаксические деревья, полученные при вызове. В ответ он получает AST, которое нужно подставить в месте вызова.


Особенности идеологий лицензирования


Мне нравится идеология свободного ПО, но и в ней бывают забавные особенности.


Когда-то, лет десять назад, я обновил свой Debian stable и, задумавшись про синтаксис какой-то команды, привычно набрал man <команда>, на что получил исчерпывающее описание вроде «[имя программы] — это программа с документацией, распространяемой под лицензией GNU GFDL с неизменяемыми секциями, которая не является DFSG-free». Говорят, эту программу написали какие-то злые проприетарщики из какого-то FSF... (Сейчас гуглится обсуждение.)


А какая-то маленькая, но важная библиотека некоторыми дистрибутивами считается несвободным ПО, поскольку к стандартной пермиссивной лицензии автор сделал приписку про то, что эта программа должна использоваться для добра, а не для зла. Смех смехом, а я бы тоже, наверное, побоялся такое в production брать — мало ли, какие представления о добре и зле у автора.


Всякое разное


Особенности интернационального компиляторостроения в период закона Мура


Суровые разработчики LLVM ограничили поддерживаемое выравнивание:


The maximum alignment is 1 << 29.

Как говорится, заставляет сначала посмеяться, а потом задуматься: первая мысль — да кому нужно выравнивание на 512 MiB. Потом почитал про разработку ядра на Rust, а там предлагают сделать структуру «таблица страниц», выровненную на 4096 байтов. А как почитаешь Википедию, так там вообще:


A full mapping hierarchy of 4 KB pages for the whole 48-bit space would take a bit more than 512 GB of memory (about 0.195% of the 256 TB virtual space).

Версия формата — как хранить?


Как-то раз решил я разобраться, почему не работает экспорт в одной программе, а он, оказывается, работает… Или нет?


Позапускав вручную команды backend-а, понял, что, в принципе, всё в порядке, просто версия должна передаваться как "2.0", а уходит просто "2". Предвкушая тривиальное исправление путём правки строковой константы, обнаруживаю функцию double getVersion() — ну а что, major есть, minor есть, даже точка есть! Впрочем, в итоге всё решилось не намного сложнее, чем предполагалось, я просто повысил точность вывода переправил тип данных и пробросил строки.


О разнице между теоретиками и практиками


По моему, где-то на Хабре я уже видел перевод статьи про то, какова минимальная падающая при запуске, но всё же компилирующаяся программа на C? int main; — символ main есть, и технически, можно передать на него управление. sirikid правильно подметил, что даже байты int тут лишние. В общем, даже говоря про программу размером 9 байтов, лучше не разбрасываться утверждениями, что она самая маленькая... Правда, программа при этом упадёт, но правилам это вполне соответствует.


Итак, ронять то, что должно работать, мы умеем, а как насчёт того, чтобы запустить не запускаемое?


$ ldd /bin/ls
        linux-vdso.so.1 (0x00007fff93ffa000)
        libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f0b27664000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0b2747a000)
        libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f0b27406000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f0b27400000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f0b278e9000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f0b273df000)
$ /lib/x86_64-linux-gnu/libc.so.6

… а libc ему человеческим голосом:


GNU C Library (Ubuntu GLIBC 2.28-0ubuntu1) stable release version 2.28.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 8.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

Программисты играют в гольф


Есть целый сайт на StackExchange, посвященный Code Golf — соревнованиям с стиле «Решите эту задачу с минимальным штрафом, зависящим от размера исходного кода». Сам формат предполагает весьма изысканные решения, но иногда они становятся уж очень изысканными. Поэтому в одном из вопросов была собрана коллекция стандартных запрещённых лазеек. Мне особенно нравится эта:


Using MetaGolfScript
MetaGolfScript is a family of programming languages. For example, the empty program in MetaGolfScript-209180605381204854470575573749277224 prints "Hello, World!".

Одной строкой



Наконец, откуда такое название статьи? Это перефразированный трюк из вывода компилятора emcc из состава Emscripten:


$ emcc --help
...
emcc: supported targets: llvm bitcode, javascript, NOT elf
(autoconf likes to see elf above to enable shared object support)

Комментарии (12)


  1. MMik
    04.02.2019 13:34

    Из исходников Asterisk IP PBX (ссылка), модуль Asterisk Manager:

    	/* Mark: If there's one thing you learn from this code, it is this...
    	   Never, ever fly Air France.  Their customer service is absolutely
    	   the worst.  I've never heard the words "That's not my problem" as
    	   many times as I have from their staff -- It should, without doubt
    	   be their corporate motto if it isn't already.  Don't bother giving
    	   them business because you're just a pain in their side and they
    	   will be sure to let you know the first time you speak to them.
    
    	   If you ever want to make me happy just tell me that you, too, will
    	   never fly Air France again either (in spite of their excellent
    	   cuisine).
    
    	   Update by oej: The merger with KLM has transferred this
    	   behaviour to KLM as well.
    	   Don't bother giving them business either...
    
    	   Only if you want to travel randomly without luggage, you
    	   might pick either of them.
    
    	*/


  1. Cerberuser
    04.02.2019 16:03
    +1

    Some people like to tile their 24" screens with a 6x4 matrix of 80x24 xterms and use vi in all of them.
    По одному экземпляру vi на каждый дюйм диагонали? Это точно были челябинские программисты…

    кодогенерация в Scala (та, которая макросы), устроена приблизительно так: вы пишите код на Scala, использующий API компилятора, и компилируете его. Потом при следующем запуске компилятора вы просто передаёте получившийся кодогенератор в classpath самого компилятора, а тот, увидев специальную директиву, его вызывает, передав синтаксические деревья, полученные при вызове. В ответ он получает AST, которое нужно подставить в месте вызова.
    Если я правильно понимаю, в Rust процедурные макросы работают точно так же (обычные — несколько проще): делается отдельный пакет, который на вход получает цепочку токенов (всё-таки не AST, да), поданную в макрос, а на выход подаёт цепочку токенов, которой вызов макроса нужно заменить.

    к стандартной пермиссивной лицензии автор сделал приписку про то, что эта программа должна использоваться для добра, а не для зла
    Шутки шутками, а в одной программе для редактирования ID3-тэгов её лицензия прямо запрещает использовать её в военных целях…


    1. atrosinenko Автор
      04.02.2019 16:16
      +1

      Шутки шутками, а в одной программе для редактирования ID3-тэгов её лицензия прямо запрещает использовать её в военных целях…

      Ну, тут хоть приблизительно описано, для чего использовать нельзя, хотя и не СПО, конечно, но её использование выглядит не так рискованно.


    1. WooHoo
      05.02.2019 09:00

      Some people like to tile their 24" screens with a 6x4 matrix of 80x24 xterms and use vi in all of them.

      Тут имеется в виду 6x4 матрица с xterm-ами размером 24 ряда, 80 колонн.


      1. Cerberuser
        05.02.2019 09:02
        +1

        Так и я о том же. 6 xterm-ов по горизонтали, 4 по вертикали, итого 24 штуки.


        1. atrosinenko Автор
          05.02.2019 09:06

          WooHoo, наверное, хочет сказать, что сравнивать "площадь" (38 попугаев 24 икстерма) и диагональ (24 дюйма) — размерность не сходится.


          1. Cerberuser
            05.02.2019 09:07

            Строго говоря, при известном соотношении сторон дисплея размерность таки сойдётся, потому что площадь с любым размером, в том числе и с диагональю, будет соотноситься взаимно однозначно. Хотя замечание уместное, признаю.


            1. webkumo
              05.02.2019 11:54

              А вот результат рассчётов — не очень. По моим прикидкам в каждом терминале по 3 дюйма диагонали наберётся...


  1. maxzhurkin
    04.02.2019 21:45

    Мои соображения по поводу undefined behavior: есть только 4 варианта, в которые укладываются все ЯП:

    1. синтакическая мощь языка больше семантической, UB во всей красе
    2. пространства синтаксиса и семантики пересекаются, случаи UB существуют
    3. пространство синтаксиса и семантики полностью совпадают, UB не может быть в той же степени, как и такого ЯП
    4. семантическая мощь больше синтаксической, UB невозможен — очень печальное зрелище, IMHO, поскольку некоторые возможности языка невозможно реализовать в силу ограниченности его синтаксиса

    P.S. Возможно, я не сам это придумал


  1. Sirikid
    05.02.2019 00:57

    Если игнорировать предупреждения, то самая маленькая программа будет main;.


    1. atrosinenko Автор
      05.02.2019 08:44

      Да уж, даже говоря про программу размером в 10, байт лучше не употреблять термин самая маленькая. Ну, будем считать, что это была версия под C++ :)


  1. Cheater
    05.02.2019 19:20
    +1

    Из документации к ядру Linux:


    Software Interrupt Context: Softirqs and Tasklets

    (...)
    The name 'tasklet' is misleading: they have nothing to do with 'tasks', and probably more to do with some bad vodka Alexey Kuznetsov had at the time.