Aller au contenu
EN FR

Architecture du générateur ABI

Statut documentaire : architecture — voir Maturité et preuves.

Rôle

Le générateur transforme la surface Runtime explicitement publiée en un contrat d'interopérabilité vérifiable, puis en bindings idiomatiques pour les écosystèmes supportés.

Il ne copie pas les classes internes du moteur et ne publie pas leur disposition mémoire. Sa responsabilité est de produire une projection publique stable.

flowchart LR
    PUB[Surface Runtime publiée] --> MODEL[Modèle de publication canonique]
    MODEL --> MANIFEST[Manifest ABI]
    MODEL --> NATIVE[Exports ABI locaux]
    MODEL --> DOTNET[Binding .NET]
    MODEL --> FUTURE[Bindings futurs]
    MANIFEST --> TESTS[Tests de contrat]

Frontière de certification actuelle

Le modèle du générateur est volontairement plus large que l'executor générique actuellement certifié dans le moteur. Dans les sources actuelles, le vocabulaire de manifeste décrit déjà strings, arrays, records, callbacks, bytes, entiers larges et flottants, tandis que le chemin natif générique est actuellement testé de bout en bout pour i32, pointeur, handle et les résultats void.

Les sections ci-dessous sur tableaux, callbacks, métadonnées async/cancellation et projections scalaires riches décrivent donc l'architecture cible de publication/génération, et non une promesse que chaque type est déjà exécutable via le marshaller ABI générique actuel. Voir Marshalling et types de valeurs pour le sous-ensemble certifié.

Pipeline recommandé

1. Découverte de la surface publiée

Le générateur ne doit considérer que les types et membres explicitement destinés à l'interopérabilité. Une classe interne ne devient jamais publique par simple découverte réflexive.

Chaque membre publié doit fournir suffisamment d'information pour déterminer :

  • son nom public ;
  • son identifiant stable ;
  • ses paramètres ;
  • sa valeur de retour ;
  • sa nullabilité ;
  • son ownership ;
  • ses capacités async/cancellation ;
  • ses types imbriqués ou éléments de collection.

2. Construction d'un modèle canonique

Avant de produire du C#, du Rust ou un export natif, le générateur construit un modèle indépendant du langage.

Cette étape empêche les conventions d'un langage d'implémentation de fuir dans les SDK publics et garantit qu'un même contrat peut être projeté vers plusieurs écosystèmes.

3. Fermeture des types publics

Tous les types accessibles depuis une signature publiée doivent eux-mêmes posséder une projection publique valide.

Une génération correcte a deux issues possibles :

  1. la signature complète est projetée ;
  2. la génération échoue avec un diagnostic précis.

Un pointeur opaque utilisé par défaut pour masquer un type inconnu n'est pas une solution acceptable pour une API publique.

4. Génération du manifest

Le manifest est le contrat machine-readable de la publication. Il doit être utilisable indépendamment du code C# généré.

Exemple conceptuel :

{
  "bindingVersion": "2",
  "abiVersion": "3",
  "capabilities": ["object-arrays", "callbacks"],
  "types": [
    {
      "typeId": 1201,
      "publicName": "Worker",
      "members": []
    }
  ]
}

Les valeurs ci-dessus sont illustratives ; les identifiants effectifs proviennent du manifest généré pour une release donnée.

5. Génération des exports locaux

La couche locale traduit le contrat canonique en opérations utilisables par le runtime client natif. Elle doit préserver :

  • identité et affinité du runtime ;
  • descripteurs de types ;
  • ownership ;
  • erreurs structurées ;
  • callbacks ;
  • tableaux et collections supportés.

Les détails de packing restent internes au binding.

6. Projection par écosystème

Chaque backend applique les conventions de son langage sans changer le sens du contrat.

Pour .NET :

  • types publics en PascalCase ;
  • paramètres et variables locales en camelCase ;
  • IDisposable pour les wrappers propriétaires ;
  • CancellationToken uniquement lorsque l'annulation est réellement supportée ;
  • suffixe Async uniquement pour des opérations véritablement asynchrones.

Les futurs bindings devront suivre le même principe : idiomatiques au niveau source, identiques au niveau sémantique.

Support des collections

Le contrat cible doit traiter uniformément les tableaux unidimensionnels de valeurs primitives, chaînes et objets publiés.

Une collection d'objets n'est pas un simple tableau de handles : chaque élément conserve son identité Runtime, son affinité et son ownership. La projection doit reconstruire le wrapper public approprié pour chaque objet retourné.

Callbacks

Le générateur doit produire des signatures de callbacks typées et documenter leur durée de vie.

Le binding est responsable de retenir les delegates ou callbacks aussi longtemps que le Runtime peut les invoquer. Le code applicatif ne doit pas manipuler directement des pointeurs de fonction lorsqu'un wrapper public existe.

Contrats async et cancellation

Le manifest doit exprimer explicitement les capacités :

  • synchrone ;
  • asynchrone ;
  • attente annulable côté client ;
  • transport annulable ;
  • annulation coopérative Runtime ;
  • annulation de job durable.

Cela évite d'inférer une sémantique à partir du nom de méthode ou de la topologie.

Tests générés

La génération d'un binding doit être accompagnée de tests de contrat. Au minimum :

  • scalaires ;
  • chaînes ;
  • nullabilité ;
  • enums ;
  • tableaux ;
  • objets ;
  • ownership ;
  • callbacks ;
  • erreurs ;
  • local/RPC lorsque la capacité est disponible.

La documentation elle-même doit pouvoir vérifier les symboles publics utilisés dans ses exemples contre le manifest correspondant à la release.

Règle de documentation

Les exemples de ce site utilisent uniquement les noms publics du binding. Les identifiants privés ou les conventions du langage d'implémentation du moteur ne font pas partie du vocabulaire développeur.

Voir aussi :