Skip to content

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:

  1. FfiMechanism абстракция: определяет четыре операции, которые механизм должен реализовать (загрузка библиотеки, разрешение символов, маршалинг, вызов)
  2. Метка механизма как выбор механизма: Native.c / Native.wasm / Native.python соответственно выбирают зарегистрированный механизм
  3. Компиляционная регистрация механизмов: метка механизма проверяется во время компиляции, незарегистрированная метка вызывает ошибку компиляции
  4. Статическая vs динамическая загрузка: оба режима сохраняют границы безопасности RFC-026

Мотивация

RFC-026 содержит только встроенный C ABI (Native.c). Но YaoXiang в будущем может потребоваться:

  • Вызывать Wasm модули (Native.wasm)
  • Встраивать Python расширения (Native.python)
  • Пользовательские ABI (проприетарное оборудование, RPC-мосты)

Вместо жёсткого кодирования этих ABI в компиляторе, лучше абстрагировать "как загружать библиотеки, как разрешать символы, как маршалировать, как вызывать" в виде одного trait, где каждый механизм реализуется как плагин. Ядро знает только FfiMechanism, не зная ничего о конкретном ABI.

Ограничения дизайна

  1. Проверка метки механизма во время компиляции: xxx в Native.xxx(...) должен быть зарегистрированным механизмом, иначе ошибка компиляции
  2. Отсутствие жёстко закодированных механизмов: компилятор не содержит встроенного списка механизмов (кроме .c как эталонной реализации), механизмы регистрируются плагинами
  3. Сохранение границ безопасности RFC-026: любой механизм должен соблюдать двоичное разделение типов, изоляцию временной области маршалинга, Move + RAII
  4. Совместимость с самозагрузкой: реестр механизмов деградирует до YaoXiang Dict/Set

Предложение

1. Абстракция FfiMechanism

Каждый FFI механизм реализует четыре операции. Это ключ к тому, почему ядро не жёстко кодирует ABI — компилятор только вызывает этот интерфейс, не зная, что за ним: C, Wasm или что-то другое:

rust
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. Метка механизма как выбор механизма

yaoxiang
// .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:

text
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Декларация доверена, проверка при загрузке
yaoxiang
// Статический: 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:

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). Интерфейс един: загрузка, разрешение, маршалинг, вызов.


Компромиссы

Преимущества

  1. Нулевое жёсткое кодирование ABI: ядро знает только FfiMechanism, новый ABI = новый плагин
  2. Единые границы безопасности: все механизмы обязаны соблюдать правила маршалинга RFC-026 §3
  3. Проверка механизмов при компиляции: незарегистрированный механизм вызывает ошибку компиляции, не обнаруживается в runtime
  4. Единая абстракция статики/динамики: детали реализации load_library скрыты внутри механизма

Недостатки

  1. Порог входа для написания плагинов: реализация FfiMechanism требует понимания целевого ABI + контракта маршалинга
  2. Обязанности механизма зависят от соглашения: изоляция временной области маршалинга зависит от соблюдения плагином, ядро не может принудительно проверить реализацию плагина

Стратегия реализации

Этап 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/Официальный проектный документ