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.
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 clang
wasm
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é lld
llvm
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". Cesrlib
fichiers, contrairement àstaticlib
les fichiers, sont interprétés par le compilateur Rust dans Future linkage. Cela signifie essentiellement querustc
va chercher métadonnéesrlib
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 questaticlib
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 utilisantwasm-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
perdreloc
etlinking
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).