Ответ 1
TL; DR: перейти к " Новая неделя, новые приключения ", чтобы получить "Привет от C и Rust!".
Хорошим способом было бы создать библиотеку WASM и передать ее компоновщику. rustc
есть опция для этого (и, похоже, существуют также директивы ode -c ode):
rustc <yourcode.rs> --target wasm32-unknown-unknown --crate-type=cdylib -C link-arg=<library.wasm>
Хитрость заключается в том, что библиотека должна быть библиотека, поэтому она должна содержать reloc
(и на практике linking
) секций. Emscripten, кажется, имеет для этого символ, 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
включен в RELOCATABLE
, поэтому это не обязательно, ONLY_MY_CODE
снимает некоторые дополнительные функции, но здесь тоже неважно)
Дело в том, что emcc
никогда не создавал для меня перемещаемый файл wasm
, по крайней мере, не версию, которую я загрузил на этой неделе, для Windows (я играл на этом с трудной трудностью, что ретроспективно могло бы быть не лучшей идеей). Таким образом, разделы отсутствуют, и rustc
продолжает жаловаться на <something.wasm> is not a relocatable wasm file
.
Затем идет clang
, который может генерировать перемещаемый модуль wasm
с очень простым wasm
:
clang -c <something.c> -o <something.wasm> --target=wasm32-unknown-unknown
Затем rustc
говорит: "Связывание суб -s ection закончилось преждевременно". Aw, да (кстати, моя установка Rust была совершенно новой). Потом я прочитал, что есть два clang
wasm
цели: wasm32-unknown-unknown-wasm
и wasm32-unknown-unknown-elf
, и, возможно, последние из них следует использовать здесь. Поскольку моя новая марка llvm+clang
запускает внутреннюю ошибку с этой целью, прося меня отправить отчет об ошибках разработчикам, это может быть что-то испытать на простом или среднем, например, в некоторых * nix или Mac.
Минимальная история успеха: сумма трех чисел
На данный момент я просто добавил lld
в llvm
и смог связать тестовый код вручную из файлов биткода:
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
Да, он суммирует числа, 2 в C
и 1 + 2 в Rust:
cadd.c
int cadd(int x,int y){
return x+y;
}
msum.rs
extern "C" {
fn cadd(x: i32, y: i32) -> i32;
}
#[no_mangle]
pub fn rsum(x: i32, y: i32, z: i32) -> i32 {
x + unsafe { cadd(y, z) }
}
test.html
<script>
fetch('msum.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:{
_ZN4core9panicking5panic17hfbb77505dc622acdE:alert
}
});
})
.then(instance => {
alert(instance.exports.rsum(13,14,15));
});
</script>
То, что _ZN4core9panicking5panic17hfbb77505dc622acdE
чувствует себя очень естественным (модуль скомпилирован и создан в два этапа, чтобы регистрировать экспорт и импорт, то есть как можно найти такие недостающие фрагменты) и прогнозирует кончину этой попытки: все работает, потому что нет другой ссылки на библиотеку времени выполнения, и этот конкретный метод может быть изделен/предоставлен вручную.
Боковая история: строка
Поскольку alloc
и его Layout
меня немного пугали, я пошел с основанным на векторе подходом, который использовался/использовался время от времени, например здесь или по Hello, Rust! ,
Вот пример, получивший строку "Hello from..." извне...
rhello.rs
use std::ffi::CStr;
use std::mem;
use std::os::raw::{c_char, c_void};
use std::ptr;
extern "C" {
fn chello() -> *mut c_char;
}
#[no_mangle]
pub fn alloc(size: usize) -> *mut c_void {
let mut buf = Vec::with_capacity(size);
let p = buf.as_mut_ptr();
mem::forget(buf);
p as *mut c_void
}
#[no_mangle]
pub fn dealloc(p: *mut c_void, size: usize) {
unsafe {
let _ = Vec::from_raw_parts(p, 0, size);
}
}
#[no_mangle]
pub fn hello() -> *mut c_char {
let phello = unsafe { chello() };
let c_msg = unsafe { CStr::from_ptr(phello) };
let message = format!("{} and Rust!", c_msg.to_str().unwrap());
dealloc(phello as *mut c_void, c_msg.to_bytes().len() + 1);
let bytes = message.as_bytes();
let len = message.len();
let p = alloc(len + 1) as *mut u8;
unsafe {
for i in 0..len as isize {
ptr::write(p.offset(i), bytes[i as usize]);
}
ptr::write(p.offset(len as isize), 0);
}
p as *mut c_char
}
Построено как rustc rhello.rs --target wasm32-unknown-unknown --crate-type=cdylib
... и фактически работает с JavaScript
:
jhello.html
<script>
var e;
fetch('rhello.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:{
chello:function(){
var s="Hello from JavaScript";
var p=e.alloc(s.length+1);
var m=new Uint8Array(e.memory.buffer);
for(var i=0;i<s.length;i++)
m[p+i]=s.charCodeAt(i);
m[s.length]=0;
return p;
}
}
});
})
.then(instance => {
/*var*/ e=instance.exports;
var ptr=e.hello();
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>
Это не особенно красиво (на самом деле я понятия не имею о Rust), но он делает то, что я ожидаю от него, и даже этот dealloc
может работать (по крайней мере, он дважды вызывает панику).
На этом был важный урок: когда модуль управляет своей памятью, его размер может измениться, что приводит к аннулированию объекта ArrayBuffer
и его представлений. Вот почему memory.buffer
проверяется несколько раз и проверяется после вызова в код wasm
.
И здесь я застрял, потому что этот код будет ссылаться на библиотеки времени выполнения и .rlib
-s. Самый близкий, который я мог бы получить к ручной сборке, следующий:
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
Где я должен был использовать lld
сидит в глубине инструментария, ржавчины, как .rlib
, как говорят, быть истолкован, таким образом, они связаны с Rust
набора инструментов
--crate-type=rlib
,#[crate_type = "rlib"]
- Будет создан файл библиотеки Rust. Это используется как промежуточный артефакт и может рассматриваться как "статическая библиотека Rust". Эти файлыrlib
, в отличие от файловstaticlib
, интерпретируются компилятором Rust в будущем соединении. Это означает, чтоrustc
будет искать метаданные в файлахrlib
например, ищет метаданные в динамических библиотеках. Эта форма вывода используется для создания статически связанных исполняемых файлов, а такжеstaticlib
выходовstaticlib
.
Конечно, этот lld
не ест файлы .wasm
/.o
сгенерированные с clang
или llc
("Связывание суб -s ection закончилось преждевременно"), возможно, часть Rust также должна быть перестроена с помощью пользовательского llvm
.
Кроме того, эта сборка, по-видимому, отсутствует у фактических распределителей, кроме chello
, в таблице импорта будет еще 4 записи: __rust_alloc
, __rust_alloc_zeroed
, __rust_dealloc
и __rust_realloc
. Что на самом деле может быть предоставлено от JavaScript в конце концов, просто побеждает идею позволить Rust обрабатывать свою собственную память, плюс распределитель присутствует в однопроходной rustc
... О, да, именно здесь я отказался от этого неделю (11 августа 2018 года, в 21:56)
Новая неделя, новые приключения, с Binaryen, wasm-dis/merge
Идея заключалась в том, чтобы модифицировать готовый код Rust (наличие распределителей и все на своем месте). И это работает. Пока ваш код C не имеет данных.
Доказательство кода концепции:
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;
}
Не очень обычный, но это код 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
- это то же самое, что представлено в "Side story: string")
И результат работает как
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>
Даже распределители, похоже, что-то делают (показания ptr
из повторных блоков с/без dealloc
показывают, как память не течет/течет соответственно).
Конечно, это супер-хрупкое и имеет таинственные части:
- если окончательное слияние выполняется с
-s
переключателя-s
(генерирует исходный код вместо.wasm
), а файл сборки результата компилируется отдельно (используяwasm-as
), результат будет на пару байтов короче (и эти байты где-то в самой середине исполняемого кода, а не в разделах export/import/data) - порядок слияния, файл с "Rust -O rigin" должен быть первым.
wasm-merge chello.wast rhello.wast [...]
умирает с развлекательным сообщением[wasm-validator error in module] неожиданное ложное: смещение сегмента должно быть разумным, on
[i32] (i32.const 1)
Fatal: ошибка в подтверждении вывода - вероятно, моя вина, но мне пришлось построить полный модуль
chello.wasm
(так, соchello.wasm
). Только компиляция (clang -c [...]
) привела к перемещаемому модулю, который так сильно пропустил в самом начале этой истории, но декомпилировал, что один (на.wast
) потерял именованный экспорт (chello()
):(export "chello" (func $chello))
полностью исчезает(func $chello...
становится(func $0...
, внутренняя функция (wasm-dis
теряетreloc
иlinking
разделы, помещая только замечание о них и их размер в источник сборки) - связанный с предыдущим: этот способ (создание полного модуля) данных из вторичного модуля не может быть перемещен с помощью
wasm-merge
: пока есть возможность поймать ссылки на самую строку (const char *HELLO="Hello from C";
становится константой со смещением 1024 в частности и позже упоминается как(i32.const 1024)
если это локальная константа внутри функции), этого не происходит. И если это глобальная константа, ее адрес также становится глобальной константой, номер 1024, хранящийся со смещением 1040, и строка будет называться(i32.load offset=1040 [...]
, которая начинает трудно ловить.
Для смеха этот код компилируется и работает тоже...
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);
}
... только он пишет "Hello from C" в середине пула сообщений Rust, что приводит к распечатке
Привет от Clt :: unwrap() 'по значению Err'an и Rust!
(Объяснение: 0-инициализаторы отсутствуют в перекомпилированном коде из-за флага оптимизации, -O
)
И он также поднимает вопрос о размещении libc
(хотя они определяют их без my_
, clang
упоминает strlen
и strcpy
как встроенные модули, также сообщая их правильные singatures, он не испускает код для них, и они становятся импортом для полученного модуля),