Проблема C ABI редко выглядит как ошибка на строке вызова. D-программа передаёт структуру, C читает её и возвращает код, но значение поля оказывается неверным или процесс падает только на одной архитектуре. Цена — непредсказуемый сбой на границе, где обычный unit test видит только один компилятор и одну раскладку памяти. Чем дольше ошибка живёт, тем труднее отличить формат данных от ошибки бизнес-логики.
Полевой разбор начинается с пакета, который реально пересекает ABI: byte length, endianness, поля, alignment и calling convention. Дальше нужно сопоставить его с C header и настройками компилятора. Нельзя проверять только имя struct. Два типа с одинаковыми полями могут иметь разный padding, порядок байтов или размер указателя. Пакет — это физический контракт, а не только исходный текст.
Размер структуры — первый стоп
ABI определяет, как типы и функции представлены для взаимодействия с машинным кодом. Для структуры важны не только поля, но и выравнивание. Добавленный int может изменить offsets следующего поля; на 32- и 64-битной платформе размер указателя различается. Если D и C собраны с разными ожиданиями, чтение смещается, а ошибка проявится как «неверное значение» далеко от причины.
Порядок байтов — отдельная ось. Файл может быть little-endian, а внешний протокол — big-endian; автоматическое копирование структуры не является преобразованием формата. Для числового поля запишите wire representation и проверяйте её на известном значении вроде 0x01020304. Так видно, поменялись байты или перепутана длина пакета.
| Проверка | Пример входа | Ожидаемое действие | Если пропустить |
|---|---|---|---|
| byte length | 24 байта | сверить sizeof на обеих сторонах | смещение полей |
| endianness | little-endian | декодировать число явно | неверный id или размер |
| field set | version, flags, payload | проверить обязательные поля | чтение мусора |
| alignment | offset 8 вместо 4 | сверить compiler layout | сбой на другой архитектуре |
| error code | 0 или отрицательное значение | перевести в D-ошибку | успех при частичном чтении |
Учебный валидатор пакета
Локальная функция ниже принимает пакет и описание ожидаемой структуры. Она проверяет три вещи, которые можно увидеть ещё до вызова: размер, порядок байтов и набор полей. Числа и названия в примере учебные, но сам порядок повторяет рабочую проверку. В реальном проекте contract строится из header и результатов компилятора, а не из догадки автора wrapper.
import { validateCAbiPacket } from './upgrade-2027-03.mjs';
const contract = {
byteLength: 12,
endianness: 'little',
fields: [{ name: 'version' }, { name: 'flags' }, { name: 'payload' }],
};
const packet = { byteLength: 12, endianness: 'little', version: 2, flags: 1, payload: 4096 };
const broken = { byteLength: 16, endianness: 'big', version: 2, flags: 1 };
console.log(validateCAbiPacket(packet, contract));
console.log(validateCAbiPacket(broken, contract));
// { accepted: true, errors: [] }
// { accepted: false, errors: ['размер структуры', 'порядок байтов', 'поле payload'] }Валидатор не вызывает C и поэтому не доказывает, что ABI корректен. Он делает видимыми три несовпадения до опасной операции. Для полноценного теста добавьте golden bytes, сборку маленького C helper и проверку результата на каждой целевой архитектуре. Смысл локального примера в том, что ошибка в contract table должна быть заметна раньше падения процесса.
Где заканчивается автоматическая проверка
Размер и поля можно сравнить автоматически, но ownership и смысл flags требуют чтения C API. Поле payload может быть указателем, длиной или offset внутри того же пакета. Значение 0 может означать пусто, success или null. Не называйте результат «валидным пакетом», пока не проверены эти семантические значения и код ошибки.
Особенно опасен частичный успех. C-функция могла записать структуру, но вернуть ошибку; D-код видит заполненное поле и продолжает обработку. Обёртка должна сначала проверить код возврата, затем интерпретировать output по версии и только потом отдавать его доменному коду. Версия пакета должна быть частью ключа выбора декодера, а не обычным полем, которое можно проигнорировать.
Действия по порядку
- Сохранить точный C header, compiler flags, target architecture и calling convention рядом с исходником wrapper.
- Составить layout table с размером, offset, alignment, типом и смыслом каждого поля.
- Проверить golden bytes для little/big-endian и граничных значений длины до вызова внешней функции.
- Разделить код возврата, output и ошибку; не считать частично заполненную структуру успешным результатом.
- Собрать тест на каждой поддерживаемой архитектуре и сохранить hex-пакет, версию контракта и итог проверки.
Ограничения и следующий шаг
JavaScript-валидатор не моделирует padding, pointer alignment, compiler lowering и реальные байты. Он проверяет форму contract table, поэтому не заменяет C helper, D compiler и тест на целевом ABI. Документ D описывает правила языка, но конкретный vendor header может добавлять свои packing directives, версии и ownership соглашения.
Следующий шаг — выбрать один extern(C) вызов, получить маленький golden packet и сравнить layout D/C в автоматической проверке. В отчёте оставьте hex, размер, архитектуру и код возврата. Это позволит отличить изменение компилятора от изменения данных и быстро вернуть ошибку к физической границе.
Проверяемые источники
- D Language Specification: Application Binary Interface — версия и дата: D language specification, page checked 31 July 2026. Применение: Правила D ABI используются для объяснения layout, alignment и представления типов. Граница: Спецификация не знает vendor header, compiler flags и архитектуру конкретного проекта.
- D Language Specification: Interfacing to C — версия и дата: D language specification, page checked 31 July 2026. Применение: Правила extern(C) и взаимодействия с C используются для выбора contract table и calling convention. Граница: Документация не гарантирует корректность неизвестного прототипа или ownership.
- D Language Specification: Memory Safety — версия и дата: D language specification, page generated 23 July 2026. Применение: Граница memory safety используется для отделения безопасного анализа пакета от raw pointer операций. Граница: Проверка памяти не подтверждает семантику полей и код возврата внешней библиотеки.