В процессе разработки языка программирования Nim 3.0 развивается новый компилятор Nimony, основополагающим принципом проектирования которого является достижение предсказуемости времени выполнения в худшем случае (Worst Case Execution Time, WCET). Это требование продиктовано ориентацией на системы жёсткого реального времени, где недетерминированное поведение недопустимо. Как следствие, архитектура Nimony исключает использование JIT-компиляторов и сборщиков мусора с трассировкой (tracing garbage collectors), поскольку их операции могут вносить непредсказуемые задержки.
Для достижения предсказуемости, примитивные типы данных (целые числа, символы) напрямую отображаются на машинные слова и байты соответствующей архитектуры. Композитные типы (структуры, объекты) формируются без использования косвенной адресации (indirection), размещаясь непосредственно в стеке или внутри других структур данных. Такой подход минимизирует накладные расходы и обеспечивает более прозрачное соответствие между исходным кодом и генерируемым машинным кодом.
В области автоматического управления памятью (MM), Nimony отходит от многообразия опций, доступных в Nim 2.0, предлагая единственный стандартизированный режим: "mm:atomicArc". Данный режим базируется на подсчёте ссылок с использованием атомарных операций, дополненном семантикой перемещения (move semantics) и вызовом деструкторов при уничтожении объекта, что сближает подход с практиками, принятыми в Rust и современном C++.
Ключевым нововведением является явное разделение объектов на ацикличные и потенциально цикличные. По умолчанию объекты считаются ацикличными (.acyclic), что является новым поведением. Для типов данных, экземпляры которых могут формировать циклические ссылки, требуется явная аннотация прагмой .cyclic. Отмечается, что ведётся разработка нового алгоритма сборки циклических ссылок, однако его готовность к промышленному использованию на данный момент не гарантирована. Преимуществом MM на основе деструкторов называется его композируемость: управление ресурсами, требующими освобождения (например, файловые дескрипторы, сетевые сокеты, каналы), интегрируется естественным образом через деструкторы соответствующих типов.
Подход к обработке ошибок в Nimony претерпел значительные изменения. Автор Nim выражает неудовлетворённость традиционными механизмами исключений и их эмуляцией через алгебраические типы данных (sum types). Вместо этого предлагается концепция интеграции состояния ошибки непосредственно в сам объект данных. В качестве примеров приводятся: представление ошибки в потоках ввода-вывода через специальное состояние, использование NaN для чисел с плавающей запятой, или low(int) для невалидных целочисленных значений. В случаях, когда объект не может инкапсулировать состояние ошибки, предлагается использовать потоко-локальную (thread-local) переменную для сигнализации.
Тем не менее, традиционный механизм исключений Nim сохраняется, но с одним важным уточнением: любая процедура, способная порождать исключение, теперь должна быть в обязательном порядке аннотирована прагмой {.raises.}. Это требование направлено на явное обозначение потенциальных нелокальных переходов управления.
В качестве альтернативы или дополнения вводится новый перечислимый тип ErrorCode. Данный тип является типобезопасным и требует исчерпывающей обработки всех возможных вариантов (аналогично case для enum). ErrorCode спроектирован с учётом возможности отображения на стандартные коды ошибок различных систем и протоколов, таких как POSIX errno, коды ошибок Windows API и статусы HTTP. Цель — унифицировать обработку ошибок между различными библиотеками и обеспечить возможность прямой трансляции системных ошибок (например, "диск переполнен") в соответствующие коды состояния (например, HTTP 507) без дополнительных преобразований. Использование ErrorCode также позволяет обрабатывать и распространять ошибки без выделения памяти в куче, что критично для обработки ситуаций нехватки памяти (OOM).
Обработка ситуаций исчерпания памяти (Out of Memory, OOM) в Nimony реализована с отходом от распространенной практики аварийного завершения программы ("die on OOM"). Вместо этого предлагается механизм, позволяющий приложению продолжить работу. Контейнеры и операции выделения памяти, которые не могут выполнить запрос, вызывают переопределяемый обработчик oomHandler. Реализация по умолчанию записывает размер неудавшегося запроса в потоко-локальную переменную и позволяет выполнению продолжиться. Состояние нехватки памяти для текущего потока может быть проверено вызовом threadOutOfMem().
Разработчик может предоставить собственную реализацию oomHandler, например, для логирования или аварийного завершения приложения, если такое поведение является предпочтительным. Важным аспектом является обработка операций конструирования ссылочных объектов (ref object), которые могут завершиться неудачей из-за OOM. В Nimony результат таких операций (например, через new или аналогичные конструкторы) может быть nil, и компилятор форсирует обработку этого случая аналогично работе с опциональными типами (Option), предотвращая таким образом ошибки разыменования нулевого указателя. В контексте процедур, аннотированных {.raises.}, возвращаемое значение nil может быть автоматически преобразовано в ErrorCode.OutOfMemError.
Механизм обобщённого программирования (generics) в Nimony получил развитие по сравнению с Nim 2.0. Ключевое улучшение заключается в том, что полная проверка типов обобщённого кода теперь выполняется на этапе его определения, а не только при инстанцировании конкретными типами. Ожидается, что это позволит выявлять ошибки на более ранних стадиях компиляции, предоставлять более информативные сообщения об ошибках и улучшить поддержку со стороны средств разработки (IDE), в частности, автодополнение кода.
Концепции (concepts), уже присутствующие в Nim, сохраняют свою роль как механизм статического описания требований к типам-параметрам обобщённых функций и типов. Они позволяют формально указать, каким операциям или свойствам должен удовлетворять тип для использования в данном обобщённом контексте.
Nimony стремится к унификации моделей асинхронного и многопоточного программирования под единой конструкцией spawn. Решение о том, будет ли задача, запущенная через spawn, выполняться в том же потоке (асинхронно) или в отдельном потоке из пула (многопоточно), принимается планировщиком во время выполнения (runtime). Это накладывает определённые требования на аргументы, передаваемые в spawn: они должны быть потокобезопасными.
Внутренняя реализация модели конкурентности будет основана на продолжениях (continuations), а компилятор будет выполнять преобразование программы в стиль передачи продолжений (Continuation-Passing Style, CPS). Отмечается, что сама конструкция spawn реализована не как встроенная возможность языка, а как плагин компилятора.
Параллелизм рассматривается как более простая задача по сравнению с конкурентностью. Для написания чисто параллельного кода, ориентированного на вычисления (например, обработка массивов данных), Nimony предложит специальные конструкции, такие как параллельные циклы for, обозначаемые оператором "||". Это позволит реализовывать параллельные алгоритмы без необходимости использования переменных потока управления (flow vars), что может быть удобно для задач научных вычислений или программирования для GPU.
Система метапрограммирования Nim, известная своими макросами, в Nimony эволюционирует в сторону плагинов компилятора (compiler plugins). Плагины представляют собой код, который компилируется в нативные инструкции и выполняется на поздних стадиях работы компилятора, после этапа проверки типов. Это даёт плагинам доступ к полной информации о типах и семантике анализируемого кода.
Обещаны улучшенные и более удобные API для разработки плагинов. Использование промежуточного формата NIF (Nim Intermediate Format) также должно упростить реализацию различных трансформаций кода. Плагины могут выполняться инкрементально и параллельно, что способствует повышению производительности компиляции.
Типы плагинов:
- Плагины для шаблонов (Template Plugins): Привязываются к конкретным шаблонам и обрабатывают код, связанный с их вызовами.
- Модульные плагины (Module Plugins): Получают на вход AST (Abstract Syntax Tree) всего модуля и и должны вернуть преобразованное дерево. Конструкция spawn является примером реализации через модульный плагин.
- Плагины для итераторов: Аналогичны плагинам для шаблонов, но применяются к итераторам.
- Плагины для номинальных типов: Могут быть привязаны к определённым типам данных, заменяя механизм "макросов переписывания термов" (term rewriting macros) из Nim. Это позволяет реализовывать оптимизации, такие как устранение временных объектов при выполнении матричных операций.
Для документации Nimony предусмотрен сайт, полностью сгенерированный ИИ и проверенный автором Nim на достоверность.
Источник: https://www.opennet.ru/opennews/art.shtml?num=63182