Память-безопасное встроенное ассемблерное кодирование в Fil-C

Встроенное ассемблерное кодирование (inline assembly), мощная, но опасная возможность в C/C++. Fil-C разработал систему валидации, которая парсит x86_64 ассемблерные инструкции и проверяет, согласованы ли они с объявленными ограничениями компилятора. Если программист ошибётся в синтаксисе (забудет пометить регистр как clobbered или неправильно укажет ограничение), компилятор не молча скомпилирует неправильный код, а вызовет панику или illegal instruction trap.

Реализация использует парсер x86_64 AT&T синтаксиса, базу данных инструкций и валидацию ограничений. Для простых инструкций (cpuid, xgetbv, сдвиги) это работает хорошо, оставляя сложные случаи (доступ к памяти, системные вызовы) вне области поддержки пока. Система ещё не в выпуске (версия 0.679), её нужно собирать из исходников.

Ключевые факты

  • Парсер проверяет x86_64 ассемблер на AT&T синтаксисе и отклоняет инструкции с эффектами памяти или неопределённым поведением
  • Валидация ограничений гарантирует, что все побочные эффекты (clobbers) указаны правильно, иначе компилятор паникует вместо молчаливого баг-скомпилирования
  • Поддерживаемые инструкции: cpuid, xgetbv, арифметические операции со сдвигами, fence-инструкции (lfence, sfence, mfence)
  • Не поддерживаются: встроенный ассемблер с доступом к памяти, системные вызовы, манипуляции со стеком x87
  • Реализация написана с помощью LLM-агента (Kimi K2.7-code), которому дали промпт с описанием цели и условиями валидации

Ред. Парсер ассемблера, который раньше отпугивал автора своей занудностью, написал LLM-агент по промпту. Самую безопасную часть фичи доверили инструменту, чью корректность доказать сложнее всего.

Почему это важно

Встроенное ассемблерное кодирование необходимо для криптографии, SIMD-инструкций, атомарных операций и низкоуровневой оптимизации. Но тонкие ошибки в ограничениях регистров приводят к молчаливым неправильно скомпилированным кодом, который потом трудно отлаживать. Fil-C решает это: неправильный inline assembly вызовет понятную ошибку при запуске, а не создаст непредсказуемое поведение.

Ред. Молчаливо неправильный код заменили на честную панику при запуске. Прогресс измеряется тем, что ошибка теперь хотя бы громко падает, а не тихо портит вам память полгода.

Кому это важно

Разработчики криптографических библиотек (OpenSSH, примеры со сдвигами в ассемблере для constant-time). Авторы SIMD-кода (zstd, simdutf, simdjson), которые используют cpuid для определения возможностей процессора. Системные программисты, работающие с фенсами памяти. Все, кто пишет на C/C++ с требованиями безопасности и использует встроенный ассемблер.

Ред. Авторам криптобиблиотек и SIMD-кода, то есть ровно тем, кто пишет constant-time ассемблер и меньше всего хочет сюрпризов от компилятора. Аудитория узкая, цена ошибки у неё максимальная.

Как это применить

Чтобы опробовать, нужно собрать Fil-C из исходников (0.679 не включает эту фичу). Затем встроенный ассемблер работает как обычно: пишешь asm() блок, указываешь ограничения и clobbers. Компилятор проверит согласованность. Если всё правильно, код скомпилируется. Если ошибка в ограничениях, то вместо неправильной программы получишь panic или illegal instruction, что проще отладить.

Ред. Чтобы опробовать память-безопасный ассемблер, надо собрать всё из исходников, потому что в релиз фича пока не попала. Применимость на сегодня примерно нулевая, но звучит красиво.

Можно ли доверять

Подход основан на стандартных инструментах LLVM и парсерах x86_64. Для cpuid и xgetbv, инструкции полностью детерминированы и безопасны (не меняют состояние программы, только читают). Для арифметических операций компилятор требует явного указания всех побочных эффектов. Ограничение в том, что пока не поддерживается ассемблер с доступом к памяти, но это осторожный выбор, сложнее валидировать.

Ред. cpuid и xgetbv детерминированы и проверяемы, тут спорить не о чем. Доверие держится на том, что самое опасное (доступ к памяти) предусмотрительно вынесли за скобки.

Риски и подводные камни

Системные вызовы не поддерживаются, нужно использовать API Fil-C (pizlonated_syscalls.h). Встроенный ассемблер с доступом к памяти невозможен, большой класс кода остаётся вне Fil-C. x87 floating point требует правильного управления стеком, что не гарантируется. Парсер видит только LLVM IR представление, где часть информации потеряна, поэтому возможны ложные отклонения правильного кода.

Ред. Системные вызовы, память, стек x87 не поддерживаются, то есть «большой класс кода остаётся вне Fil-C». Безопасный ассемблер пока безопасен ровно потому, что умеет почти ничего.

«Before the advent of AI, writing a parser for x86_64 assembly would have been such an annoying task that I might have never gotten around to implementing support for memory safe inline assembly... But now, implementing a feature like this is as simple as writing a good prompt!»

— Автор Fil-C о использовании LLM для разработки парсера