Comment utiliser une bibliothèque C dans une bibliothèque Rust compilée pour WebAssembly?

j'expérimente L'interopérabilité Rust, WebAssembly et C pour éventuellement utiliser la bibliothèque Rust (avec dépendance statique C) dans le navigateur ou le noeud.js. Je suis à l'aide de wasm-bindgen pour le code de colle JavaScript.

#![feature(libc, use_extern_macros)]
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;
use std::os::raw::c_char;
use std::ffi::CStr;

extern "C" {
    fn hello() -> *const c_char; // returns "hello from C" 
}

#[wasm_bindgen]
pub fn greet() -> String {
    let c_msg = unsafe { CStr::from_ptr(hello()) };
    format!("{} and Rust!", c_msg.to_str().unwrap())
}

Ma première approche naïve était d'avoir un build.rs script qui utilise la caisse gcc pour générer une bibliothèque statique à partir du code C. Avant d'introduire les bits WAM, je pouvais compiler le programme Rust et voir le hello from C sortie dans la console, maintenant I obtenir une erreur du compilateur disant

rust-lld: error: unknown file type: hello.o

construire.rs

extern crate gcc;                                                                                         

fn main() {
    gcc::Build::new()
        .file("src/hello.c")
        .compile("libhello.a");
}

Cela fait sens, maintenant que j'y pense, depuis le hello.o le fichier a été compilé pour l'architecture de mon ordinateur portable pas WebAssembly.

idéalement, je voudrais que cela fonctionne à partir de la boîte ajoutant un peu de magie dans ma construction.rs qui compilerait par exemple la bibliothèque C pour être une bibliothèque WebAssembly statique que Rust peut utiliser.

Ce que je pense qui pourrait fonctionner, mais souhaitez eviter depuis qu'il semble plus problématique, utilise Emscripten pour créer une bibliothèque WAM pour le code C puis compiler la bibliothèque Rust séparément et les coller ensemble en JavaScript.

18
demandé sur olanod 2018-08-03 09:44:17

1 réponses

TL;DR: Sauter "Nouvelle semaine, nouvelles aventures" afin d'obtenir "Bonjour de C et de la Rouille!"

la bonne façon serait de créer une bibliothèque WASM et de la Passer au linker. rustc a une option pour cela (et il semble y avoir des directives source-code aussi):

rustc <yourcode.rs> --target wasm32-unknown-unknown --crate-type=cdylib -C link-arg=<library.wasm>

Le truc, c'est que la bibliothèque doit être une bibliothèque, donc il doit contenir reloc (et dans la pratique linking) sections. Emscripten semble avoir un symbole pour cette, RELOCATABLE:

emcc <something.c> -s WASM=1 -s SIDE_MODULE=1 -s RELOCATABLE=1 -s EMULATED_FUNCTION_POINTERS=1 -s ONLY_MY_CODE=1 -o <something.wasm>

(EMULATED_FUNCTION_POINTERS est inclus avec RELOCATABLE, il n'est pas vraiment nécessaire, ONLY_MY_CODE bandes de quelques extras, mais il n'a pas d'importance ici)

Le truc, c'est que emcc jamais généré un délocalisables wasm fichier pour moi, au moins pas la version que j'ai téléchargé cette semaine, pour Windows (j'ai joué en difficile, ce qui rétrospectivement pourraient ne pas avoir été la meilleure idée). Si les articles sont manquants et rustc garde de se plaindre <something.wasm> is not a relocatable wasm file.

Puis vient clang, qui peut générer un relogeable wasm module avec une doublure unique très simple:

clang -c <something.c> -o <something.wasm> --target=wasm32-unknown-unknown

rustc dit "sous-section de liaison terminée prématurément". Aw, oui (au fait, mon installation de rouille était toute neuve aussi). Puis, j'ai lu qu'il y a deux clangwasm objectifs: wasm32-unknown-unknown-wasm et wasm32-unknown-unknown-elf, et peut-être que le dernier devrait être utilisé ici. Comme mon aussi la marque new llvm+clang construire heurte à une erreur interne avec cet objectif, en me demandant d'envoyer un rapport d'erreur aux développeurs, cela pourrait être quelque chose à tester sur easy ou medium, comme sur quelque *nix ou Mac box.

Histoire de réussite minimale: somme de trois nombres

À ce stade, j'ai juste ajouté lldllvm et réussi à lier manuellement un code test à partir de fichiers bitcode:

clang cadd.c --target=wasm32-unknown-unknown -emit-llvm -c
rustc rsum.rs --target wasm32-unknown-unknown --crate-type=cdylib --emit llvm-bc
lld -flavor wasm rsum.bc cadd.bc -o msum.wasm --no-entry

Aw oui, c'sommes les numéros, 2 après appel wasm code.

Et c'est là que je suis bloqué, parce que ce code serait de se référer à des bibliothèques d'exécution, et .rlib - S. Le plus proche que j'ai pu obtenir d'une construction manuelle est le suivant:

rustc rhello.rs --target wasm32-unknown-unknown --crate-type=cdylib --emit obj
lld -flavor wasm rhello.o -o rhello.wasm --no-entry --allow-undefined
     liballoc-5235bf36189564a3.rlib liballoc_system-f0b9538845741d3e.rlib
     libcompiler_builtins-874d313336916306.rlib libcore-5725e7f9b84bd931.rlib
     libdlmalloc-fffd4efad67b62a4.rlib liblibc-453d825a151d7dec.rlib
     libpanic_abort-43290913ef2070ae.rlib libstd-dcc98be97614a8b6.rlib
     libunwind-8cd3b0417a81fb26.rlib

où j'ai dû utiliser le lld assis dans les profondeurs de la Rouille de la chaîne .rlib-s sont dit interprété, donc ils sont liés à la Rust chaîne d'outils

--crate-type=rlib,#[crate_type = "rlib"] - un fichier "Rust library" sera produit. Il est utilisé comme un artefact intermédiaire et peut être considéré comme une "bibliothèque de rouille statique". Ces rlib fichiers, contrairement à staticlib les fichiers, sont interprétés par le compilateur Rust dans Future linkage. Cela signifie essentiellement que rustc va chercher métadonnées rlib recherche des métadonnées dans les bibliothèques dynamiques. Cette forme de sortie est utilisée pour produire de l'statiquement exécutables ainsi que staticlib sorties.

bien sûr, ce lld ne mange pas le .wasm/.o les fichiers générés avec clang ou llc ("Linking sub-section ended prematurely"), peut-être que la partie rouillée devrait aussi être reconstruite avec la coutume llvm.

En outre, cette construction semble manquer le réel allocateurs, en outre chello, il y aura 4 plus d'entrées dans la table d'importation: __rust_alloc,__rust_alloc_zeroed,__rust_dealloc et __rust_realloc. Qui en fait pourrait être fourni à partir de JavaScript après tout, vient de battre l'idée de laisser la rouille gérer sa propre mémoire, plus un allocator était présent dans le single-pass rustc construire... Oh, oui, c'est là que j'ai abandonné pour cette semaine (Août 11, 2018, à 21:56)

Nouvelle semaine, nouvelles aventures, avec Binaryen, wasm-dis/merge

L'idée était de modifier le code rouille prêt à l'emploi (avoir des allocateurs et tout en place). Et celui-ci fonctionne. Tant que votre code C n'a pas de données.

code de validation de principe:

chello.c

void *alloc(int len); // allocator comes from Rust

char *chello(){
  char *hell=alloc(13);
  hell[0]='H';
  hell[1]='e';
  hell[2]='l';
  hell[3]='l';
  hell[4]='o';
  hell[5]=' ';
  hell[6]='f';
  hell[7]='r';
  hell[8]='o';
  hell[9]='m';
  hell[10]=' ';
  hell[11]='C';
  hell[12]=0;
  return hell;
}

pas très habituel, mais c'est un code C.

rustc rhello.rs --target wasm32-unknown-unknown --crate-type=cdylib
wasm-dis rhello.wasm -o rhello.wast
clang chello.c --target=wasm32-unknown-unknown -nostdlib -Wl,--no-entry,--export=chello,--allow-undefined
wasm-dis a.out -o chello.wast
wasm-merge rhello.wast chello.wast -o mhello.wasm -O

(rhello.rs est la même que celle présentée en "Side story: string")

Et le résultat fonctionne

mhello.html

<script>
  fetch('mhello.wasm')
    .then(response => response.arrayBuffer())
    .then(bytes => WebAssembly.compile(bytes))
    .then(module => {
      console.log(WebAssembly.Module.exports(module));
      console.log(WebAssembly.Module.imports(module));
      return WebAssembly.instantiate(module, {
        env:{
          memoryBase: 0,
          tableBase: 0
        }
      });
    })
    .then(instance => {
      var e=instance.exports;
      var ptr=e.hello();
      console.log(ptr);
      var optr=ptr;
      var m=new Uint8Array(e.memory.buffer);
      var s="";
      while(m[ptr]!=0)
        s+=String.fromCharCode(m[ptr++]);
      e.dealloc(optr,s.length+1);
      console.log(s);
    });
</script>

Même les allocateurs semblent faire quelque chose (ptr lectures et de la répétition des blocs avec/sans dealloc montrer comment la mémoire ne fuit pas / fuit en conséquence).

bien sûr, c'est super fragile et a mystérieuses trop:

  • si la fusion finale est exécutée avec -S switch (génère du code source au lieu de .wasm), et le fichier d'assemblage des résultats est compilé séparément (en utilisant wasm-as), le résultat sera un couple d'octets plus court (et ces octets sont quelque part au milieu du code courant, pas dans les sections export / import / data)
  • l'ordre de fusion est important, le fichier avec "Rust-origin" doit passer en premier. wasm-merge chello.wast rhello.wast [...] meurt avec un message divertissant

    [wasm-programme de validation d'erreur dans le module] inattendu faux: segment de décalage doit être raisonnable, sur

    [i32] (i32.const 1)

    Fatale erreur: erreur dans la validation de sortie

  • probablement de ma faute, mais j'ai dû construire un chello.wasm module (donc, avec le lien). Compilation seulement (clang -c [...]) a donné lieu au module relogeable qui a tellement manqué au tout début de cette histoire, mais en décomposant celui-ci (à .wast) ont perdu la nommée à l'exportation (chello()):

    (export "chello" (func $chello)) disparaît complètement

    (func $chello ... devient (func ..., une fonction interne (wasm-dis perd reloc et linking sections, en mettant seulement une remarque sur eux et leur taille dans la source d'assemblage)
  • lié au précédent: de cette façon (construction d'un module complet) les données du module secondaire ne peuvent pas être déplacées par wasm-merge: bien qu'il y est une chance pour la capture de références à la chaîne elle-même (const char *HELLO="Hello from C"; devient une constante à l'offset 1024 en particulier, et plus tard appelée (i32.const 1024) si elle est constante locale, à l'intérieur d'une fonction), il ne se produit pas. Et si c'est un mondial constante, son adresse devient aussi une constante globale, numéro 1024 stocké à l'offset 1040, et la chaîne va être appelée (i32.load offset=1040 [...], qui commence à être difficile à attraper.
  • emcc

    Pour rire, ce code compile et fonctionne aussi...

    void *alloc(int len);
    
    int my_strlen(const char *ptr){
      int ret=0;
      while(*ptr++)ret++;
      return ret;
    }
    
    char *my_strcpy(char *dst,const char *src){
      char *ret=dst;
      while(*src)*dst++=*src++;
      *dst=0;
      return ret;
    }
    
    char *chello(){
      const char *HELLO="Hello from C";
      char *hell=alloc(my_strlen(HELLO)+1);
      return my_strcpy(hell,HELLO);
    }
    

    ... juste il écrit "Hello from C" au milieu de la liste de messages de Rust, résultant dans l'impression

    Bonjour Clt::unwrap()` sur un `Err an de la valeur et de La rouille!

    (explication: 0-les initialiseurs ne sont pas présents dans le code recompilé à cause du drapeau d'optimisation, -O)

    Et il soulève également la question de la localisation d'un libc (bien que les définissant sans my_,clang mentionne strlen et strcpy comme intégré, indiquant également leurs singatures correctes, il n'émet pas de code pour eux et ils deviennent des importations pour le module résultant).

5
répondu tevemadar 2018-08-21 09:39:02