typescript - Rust WebAssembly Elemente Personalizate Eroare de Dealocare a Memoriei
Primul meu WASM produs cu Rust generează următoarea eroare, nu am nicio idee cum să procedez pentru a o depana.
wasm-000650c2-23:340 Uncaught RuntimeError: memory access out of bounds
at dlmalloc::dlmalloc::Dlmalloc::free::h36961b6fbcc40c05 (wasm-function[23]:670)
at __rdl_dealloc (wasm-function[367]:8)
at __rust_dealloc (wasm-function[360]:7)
at alloc::alloc::dealloc::h90df92e1f727e726 (wasm-function[146]:100)
at <alloc::alloc::Global as core::alloc::Alloc>::dealloc::h7f22ab187c7f5835 (wasm-function[194]:84)
at <alloc::raw_vec::RawVec<T, A>>::dealloc_buffer::hdce29184552be976 (wasm-function[82]:231)
at <alloc::raw_vec::RawVec<T, A> as core::ops::drop::Drop>::drop::h3910dccc175e44e6 (wasm-function[269]:38)
at core::ptr::real_drop_in_place::hd26be2408c00ce9d (wasm-function[267]:38)
at core::ptr::real_drop_in_place::h6acb013dbd13c114 (wasm-function[241]:50)
at core::ptr::real_drop_in_place::hb270ba635548ab74 (wasm-function[69]:192)
Contextul: ultima versiune de Chrome, cod Rust wasm-bindgen apelat dintr-un element personalizat TypeScript, care operează asupra unui canvas în shadow DOM. Datele afișate pe canvas provin dintr-un AudioBuffer HTML5. Toate variabilele Rust sunt cu domeniu local.
Componenta web funcționează perfect dacă apare o singură instanță în document, dar dacă apar mai multe instanțe, se generează un stack trace așa cum este prezentat mai sus. Codul rulează fără nicio altă problemă.
Știu că există erori de memorie nerezolvate în Chrome - arată ele așa sau poate un dezvoltator experimentat de Rust/Wasm să-mi spună dacă este neobișnuit?
js-sys = "0.3.19"
wasm-bindgen = "0.2.42"
wee_alloc = { version = "0.4.2", optional = true }
[dependencies.web-sys]
version = "0.3.4"
Codul Rust este mic și redă doar două canale ale unui AudioBuffer pe un HTMLCanvasElement furnizat:
#[wasm_bindgen]
pub fn render(
canvas: web_sys::HtmlCanvasElement,
audio_buffer: &web_sys::AudioBuffer,
stroke_style: &JsValue,
line_width: f64,
step_size: usize,
) {
// ...
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() }; // !
for channel_number in 0..1 {
channel_data[channel_number] = audio_buffer
.get_channel_data(channel_number as u32)
.unwrap();
}
// ...
Am încercat să comentez funcționalitatea, și dacă codul nu atinge canvasul, dar face cele de mai sus, primesc eroarea. Făcând schimbarea de mai jos duce la o simplă eroare de „memorie WAM epuizată”. Fișierul audio are 1.200 k.
let channel_data: [Vec<f32>; 2] = [
audio_buffer.get_channel_data(0).unwrap(),
audio_buffer.get_channel_data(1).unwrap()
];
EDITARE: Ultima eroare out of memory, pentru codul corect de mai sus, m-a derutat cu adevărat, dar de fapt este un bug al Chrome.
Răspuns #1
Problema ta este că creezi un bloc de memorie neinicializată și nu îl inițializezi corespunzător:
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in 0..1 {
channel_data[channel_number] = audio_buffer
.get_channel_data(channel_number as u32) // no need for `as u32` here btw
.unwrap();
}
Intervalele (Range, cunoscute și ca a..b) sunt exclusive în Rust. Aceasta înseamnă că bucla ta nu iterează de două ori așa cum ai presupune, ci doar o dată și ai un Vec<f32> neințializat care apoi va genera panică la eliberare. (Vă rugăm să consultați răspunsul lui Matthieu M. pentru o explicație corectă)
Există câteva posibilități aici.
- Folosește intervalul corespunzător, de exemplu
0..2 - Folosește un interval inclusiv
0..=1 Nu folosi construcția nesigură, ci în schimb
let mut channel_data: [Vec<f32>; 2] = Default::default()
Aceasta va inițializa corespunzător cele două Vecuri.
Pentru o prezentare mai completă despre cum să inițializezi un tablou, vezi Care este modul corect de a inițializa un tablou de lungime fixă?
Ca o paranteză: evită să folosești unsafe, mai ales dacă ești nou în Rust.
Răspunsul #2
Există două probleme aici:
- Creezi un bloc de memorie neinițializat și îl tratezi ca și cum ar fi inițializat.
- Iterația ta este greșită,
0..1iterează peste[0](este exclusiv).
Să le verificăm pe rând.
Nu folosiți unsafe.
În general, ar trebui să încerci să eviți unsafe. Există foarte puține motive pentru a-l folosi și multe moduri de a-l folosi incorect (așa cum este aici).
Problema.
În acest caz particular:
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in /*...*/ {
channel_data[channel_number] = /*...*/;
}
Există două probleme:
- Utilizarea
std::mem::uninitializedeste învechită din motive de siguranță; este o idee foarte proastă să o folosești. Înlocuitorul său esteMaybeUninitialized. - Atribuirea în memorie neinițializată este un comportament nedefinit.
Nu există operator de atribuire în Rust, pentru a efectua o atribuire limbajul va:
- Renunță la instanța anterioară.
- Suprascrie memoria care nu mai este folosită.
Eliberarea memoriei brute care crede că este un Vec este un comportament nedefinit; în acest caz, efectul probabil este că o valoare aleatorie a unui pointer este citită și eliberată. Acest lucru poate provoca o prăbușire, poate elibera un pointer nelegat, ducând la o prăbușire ulterioară sau coruperea memoriei, este RĂU.
Soluția.
Există puțin motiv pentru a folosi unsafe aici:
- Este perfect posibil să se efectueze în siguranță inițializarea tabloului.
- Este perfect posibil să se inițializeze direct tabloul.
- Există un beneficiu redus de performanță în a NU efectua inițializarea implicită dacă insistați pe o inițializare în două etape, deoarece implementarea
DefaultaVecnu alocă memorie.
Pe scurt:
auto create_channel = |channel_number: u32| {
audio_buffer
.get_channel_data(channel_number)
.unwrap()
};
let mut channel_data = [create_channel(0), create_channel(1)];
este simplu, sigur și cel mai eficient.
Prefer iteratoarele în locul indexării.
Dacă insiști asupra inițializării în doi pași, atunci folosește iteratoare în loc de indexare pentru a evita erorile de tip off-by-one.
În cazul tău:
let mut channel_data = [vec!(), vec!()];
for (channel_number, channel) = channel_data.iter_mut().enumerate() {
*channel = audio_buffer
.get_channel_data(channel_number as u32)
.unwrap();
}
Există multe funcții utilitare pe Iterator, în acest caz particular, enumerate va împacheta elementul returnat de iter_mut() (un &mut Vec<f32>) într-un tuplu (usize, &mut Vec<32>):
- Ai acces direct la element, nici o calculare nu este necesară.
- De asemenea, aveți indexul elementului, fără erori de tip off-by-one.
