RFC-026a: Расширяемая система механизмов FFI
Родительский RFC: RFC-026: Базовый механизм FFI
В данном RFC определяется часть расширяемости RFC-026 — как подключать FFI механизмы помимо C ABI (Wasm, Python, пользовательские ABI) в качестве плагинов, а также режим динамической загрузки.
Краткое описание
RFC-026 определяет базовый механизм FFI, где Native.c("lib") использует встроенный C ABI. Данный RFC абстрагирует механизм ABI в виде подключаемого FfiMechanism, чтобы ядро не было жёстко привязано к конкретному ABI:
FfiMechanismабстракция: определяет четыре операции, которые механизм должен реализовать (загрузка библиотеки, разрешение символов, маршалинг, вызов)- Метка механизма как выбор механизма:
Native.c/Native.wasm/Native.pythonсоответственно выбирают зарегистрированный механизм - Компиляционная регистрация механизмов: метка механизма проверяется во время компиляции, незарегистрированная метка вызывает ошибку компиляции
- Статическая vs динамическая загрузка: оба режима сохраняют границы безопасности RFC-026
Мотивация
RFC-026 содержит только встроенный C ABI (Native.c). Но YaoXiang в будущем может потребоваться:
- Вызывать Wasm модули (
Native.wasm) - Встраивать Python расширения (
Native.python) - Пользовательские ABI (проприетарное оборудование, RPC-мосты)
Вместо жёсткого кодирования этих ABI в компиляторе, лучше абстрагировать "как загружать библиотеки, как разрешать символы, как маршалировать, как вызывать" в виде одного trait, где каждый механизм реализуется как плагин. Ядро знает только FfiMechanism, не зная ничего о конкретном ABI.
Ограничения дизайна
- Проверка метки механизма во время компиляции:
xxxвNative.xxx(...)должен быть зарегистрированным механизмом, иначе ошибка компиляции - Отсутствие жёстко закодированных механизмов: компилятор не содержит встроенного списка механизмов (кроме
.cкак эталонной реализации), механизмы регистрируются плагинами - Сохранение границ безопасности RFC-026: любой механизм должен соблюдать двоичное разделение типов, изоляцию временной области маршалинга, Move + RAII
- Совместимость с самозагрузкой: реестр механизмов деградирует до YaoXiang
Dict/Set
Предложение
1. Абстракция FfiMechanism
Каждый FFI механизм реализует четыре операции. Это ключ к тому, почему ядро не жёстко кодирует ABI — компилятор только вызывает этот интерфейс, не зная, что за ним: C, Wasm или что-то другое:
trait FfiMechanism {
/// Метка механизма, например "c" / "wasm" / "python"
fn tag(&self) -> &str;
/// Загрузка библиотеки. C: dlopen/статическая линковка; Wasm: инстанциирование модуля; Python: import.
/// Возвращает внутренний дескриптор библиотеки механизма.
fn load_library(&self, id: &str) -> Result<LibraryHandle>;
/// Разрешение символа. Может вызываться во время компиляции для проверки существования символа.
/// C: dlsym/поиск в таблице символов; Wasm: поиск в таблице экспорта.
fn resolve(&self, lib: &LibraryHandle, symbol: &str) -> Result<SymbolHandle>;
/// Вызов. Маршалирует аргументы согласно YaoXiang сигнатуре, выполняет, маршалирует возвращаемое значение.
/// Должен соблюдать правила маршалинга RFC-026 §3 (изоляция временной области).
fn invoke(
&self,
sym: &SymbolHandle,
args: &[RuntimeValue],
sig: &Signature,
) -> Result<RuntimeValue>;
}Важно: реализация invoke должна соблюдать RFC-026 §3 — входные параметры копируются во временную область, возврат через memcpy, заимствование ограничено одним вызовом. Механизм может выбирать свои детали ABI, но не может нарушать границы безопасности. Это обязанность плагина.
2. Метка механизма как выбор механизма
// .c → Механизм C ABI (встроенная эталонная реализация RFC-026)
sqlite3 = Native.c("libsqlite3")
SqliteDb.open: (f: String) -> ?SqliteDb = sqlite3("sqlite3_open")
// .wasm → Механизм Wasm (регистрируется плагином yx_wasm_ffi)
wasm_mod = Native.wasm("mymodule.wasm")
process: (input: String) -> String = wasm_mod("process")
// .python → Механизм Python (регистрируется плагином yx_python_ffi)
np = Native.python("numpy").c / .wasm в Native.c / Native.wasm — это метки механизмов, выбирающие какой зарегистрированный FfiMechanism использовать. Ядро содержит встроенный .c как эталонную реализацию; остальные предоставляются плагинами.
3. Регистрация механизмов и проверка во время компиляции
Плагины объявляют предоставляемые ими метки механизмов во время компиляции через .so:
use yx_wasm_ffi
→ Загрузка libyx_wasm_ffi.so
→ Вызов yx_register_mechanism()
→ Регистрация FfiMechanism { tag: "wasm", ... }
→ В реестр механизмов добавлен "wasm"
// Далее:
Native.wasm("mod.wasm") // ✅ Компиляция успешна, "wasm" зарегистрирован
Native.foo("x") // ❌ Ошибка компиляции: Неизвестный FFI механизм 'foo'
// Попробуйте: `use yx_foo_ffi`Компиляционный реестр механизмов хранит только метки механизмов (строки) + указатель на экземпляр FfiMechanism. При компиляции Native.xxx(...) выполняется поиск по таблице, отсутствие метки вызывает ошибку компиляции.
4. Статическая vs динамическая загрузка
Реализация load_library определяет время загрузки, оба режима сохраняют границы безопасности RFC-026:
| Режим | Поведение load_library | Проверка символов | Тип |
|---|---|---|---|
| Статический (по умолчанию, C ABI) | Компиляционный -llib, библиотека в таблице символов | Компиляционное чтение таблицы символов | Полностью реализован |
| Динамический | dlopen/инстанциирование при первом вызове в runtime | Проверка при первой загрузке, отсутствие → fail-fast | Декларация доверена, проверка при загрузке |
// Статический: C библиотека линкуется при компиляции
sqlite3 = Native.c("libsqlite3") // Компиляционный -lsqlite3
// Динамический: плагин обнаруживается в runtime
plugin = Native.c.dynamic("./plugins/foo.so") // Runtime dlopenНезависимо от статической или динамической загрузки, маршалинг проходит через изоляцию временной области RFC-026 §3. В динамическом режиме отсутствие символа — это чистая ошибка runtime (fail-fast), не крах.
5. Полный информационный поток
use yx_wasm_ffi ← Регистрация механизма "wasm"
│
▼
wasm_mod = Native.wasm("mod.wasm")
Компиляция: Поиск "wasm" в реестре механизмов ✅
→ Вызов load_library("mod.wasm") механизма wasm
→ Инстанциирование Wasm модуля, возврат дескриптора библиотеки
│
▼
process: (input: String) -> String = wasm_mod("process")
Компиляция: Вызов resolve(lib, "process") механизма wasm для проверки экспорта ✅
→ Генерация CallNative { mechanism: "wasm", lib, symbol: "process", sig }
│
▼ Runtime
Выполнение CallNative
→ invoke(sym, args, sig) механизма
→ Маршалинг по sig (изоляция временной области) → Выполнение Wasm → Маршалинг возврата6. Деградация после самозагрузки
Trait FfiMechanism и реестр механизмов в Rust-управляемый период деградируют до обычных структур YaoXiang:
// После самозагрузки реестр механизмов — это Dict
let mechanisms: Dict(String, FfiMechanism) = {}
mechanisms["c"] = c_mechanism
mechanisms["wasm"] = wasm_mechanism
// FfiMechanism — это интерфейс в YaoXiang (RFC-011a динамическая диспетчеризация)
// Native.c("lib") → mechanisms["c"].load_library("lib")В Rust-период используется trait object (Box<dyn FfiMechanism>), после самозагрузки — YaoXiang интерфейс (RFC-011a). Интерфейс един: загрузка, разрешение, маршалинг, вызов.
Компромиссы
Преимущества
- Нулевое жёсткое кодирование ABI: ядро знает только
FfiMechanism, новый ABI = новый плагин - Единые границы безопасности: все механизмы обязаны соблюдать правила маршалинга RFC-026 §3
- Проверка механизмов при компиляции: незарегистрированный механизм вызывает ошибку компиляции, не обнаруживается в runtime
- Единая абстракция статики/динамики: детали реализации
load_libraryскрыты внутри механизма
Недостатки
- Порог входа для написания плагинов: реализация
FfiMechanismтребует понимания целевого ABI + контракта маршалинга - Обязанности механизма зависят от соглашения: изоляция временной области маршалинга зависит от соблюдения плагином, ядро не может принудительно проверить реализацию плагина
Стратегия реализации
Этап 1a: Абстракция механизма (v0.8)
- [ ] Определение trait
FfiMechanism(load_library / resolve / invoke) - [ ] Рефакторинг C ABI реализации RFC-026 в
CMechanism: FfiMechanism - [ ] Реализация компиляционного реестра механизмов (метка → экземпляр механизма)
- [ ]
Native.xxx— проверка компиляционного реестра механизмов
Этап 1b: Динамическая загрузка + плагины (v0.9)
- [ ] Реализация загрузки плагинов
.so(yx_register_mechanism) - [ ] Реализация режима динамической загрузки библиотек (
Native.c.dynamic) - [ ] Эталонный плагин:
yx_wasm_ffi(механизм Wasm)
Связь с другими RFC
- RFC-026 (родительский): Базовый механизм FFI —
FfiMechanismдолжен соблюдать правила маршалинга и границы безопасности - RFC-011a: Интерфейсы и динамическая диспетчеризация — после самозагрузки
FfiMechanismдеградирует в YaoXiang интерфейс - RFC-014: Система управления пакетами — обнаружение и загрузка
.soплагинов зависит от менеджера пакетов - RFC-021 (устаревший): Расширение FFI через драйверы библиотек — данный RFC переносит его API
ffi.load_libraryна уровень плагинов механизмов
Журнал решений по дизайну
| Решение | Решение | Причина | Дата |
|---|---|---|---|
| Абстракция механизма | trait FfiMechanism, четыре операции | Ядро не жёстко кодирует ABI, знает только интерфейс | 2026-07-03 |
| Обязанности механизма | Плагин должен соблюдать правила маршалинга RFC-026 | Границы безопасности не нарушаются из-за разных механизмов | 2026-07-03 |
| Проверка метки механизма | Проверка реестра при компиляции | Незарегистрированный механизм вызывает ошибку при компиляции | 2026-07-03 |
| Статика/динамика | Решение через реализацию load_library | Время — это деталь механизма, границы безопасности неизменны | 2026-07-03 |
| Деградация самозагрузки | trait → YaoXiang интерфейс (RFC-011a) | Отсутствие чрезмерной абстракции над хост-языком | 2026-07-03 |
Жизненный цикл и судьба
| Статус | Расположение | Описание |
|---|---|---|
| На рассмотрении | docs/design/rfc/review/ | Открыто для обсуждения сообщества |
| Принято | docs/design/rfc/accepted/ | Официальный проектный документ |
