Rust WebAssembly 自定义元素内存释放错误
Rust WebAssembly Custom Elements Memory Deallocation Error
我的第一个 Rust 生成的 WASM 产生了以下错误,我不知道如何进行调试。
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)
上下文:最新 Chrome,从 TypeScript 自定义元素调用的 Rust wasm-bindgen 代码,在阴影 DOM 中对 canvas 进行操作。呈现给 canvas 的数据来自 HTML5 AudioBuffer。所有 Rust 变量都在局部范围内。
如果文档中只出现一个实例,Web 组件将完美运行,但如果我有更多实例,则会像上面那样转储堆栈跟踪。代码运行没有任何其他问题。
我知道 Chrome 中存在显着的内存错误 -- 它们看起来像这样吗,或者有经验的 rust/wasm 开发人员可以告诉我这是否异常?
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"
rust 代码很小,只将 AudioBuffer 的两个通道渲染到提供的 HTMLCanvasElement:
#[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();
}
// ...
我试过注释掉功能,如果代码没有触及 canvas 但触及了上面的内容,我会收到错误消息。进行以下更改会导致一个简单的 'out of wam memory' 错误。音频文件是 1,200 k.
let channel_data: [Vec<f32>; 2] = [
audio_buffer.get_channel_data(0).unwrap(),
audio_buffer.get_channel_data(1).unwrap()
];
EDIT: 后面的out of memory
错误,对于上面正确的代码,真的把我吓坏了,但实际上是.
你的问题是你创建了一大块未初始化的内存并且没有正确初始化它:
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();
}
Range
s (a.k.a. a..b
) are exclusive in Rust. This means that your loop does not iterate twice as you would suppose, but instead only once and you have one uninitialized Vec<f32>
which then will panic while dropping it. (Please see 以获得正确的解释)
这里有几种可能性。
- 使用适当的范围,例如
0..2
- 使用 inclusive range
0..=1
- 不要使用不安全的结构,而是
let mut channel_data: [Vec<f32>; 2] = Default::default()
这将正确初始化两个 Vec
。
有关如何初始化数组的更完整概述,请参阅
作为旁注:避免使用 unsafe
,特别是如果您是 Rust 新手。
这里有两个问题:
- 您创建了一个未初始化的内存块,并将其视为已初始化。
- 你的迭代是错误的,
0..1
迭代了 [0]
(它是独占的)。
让我们一次检查一个。
不要使用 unsafe
。
一般来说,你应该尽量避免unsafe
。使用它的理由很少,使用不当的方法很多(比如这里)。
问题.
在这种特殊情况下:
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in /*...*/ {
channel_data[channel_number] = /*...*/;
}
有两个问题:
- 出于安全原因,不推荐使用
std::mem::uninitialized
;使用它是一个非常糟糕的主意。它的替换是 MaybeUninitialized
.
- 分配给未初始化的内存是未定义的行为。
Rust 中没有赋值运算符,为了执行赋值,语言将:
- 删除上一个实例。
- 覆盖现在未使用的内存。
丢弃认为是 Vec
的原始内存是未定义的行为;在这种情况下,可能的效果是读取并释放了一些随机指针值。这可能会崩溃,这可能会释放一个不相关的指针,导致后来的崩溃或内存损坏,这是 BAD.
解决方法。
这里没有理由使用unsafe
:
- 完全可以安全地执行数组的初始化。
- 完全可以直接初始化数组
- 如果您坚持两步初始化,不执行默认初始化几乎没有性能优势,因为
Vec
的 Default
实现不分配内存。
简而言之:
auto create_channel = |channel_number: u32| {
audio_buffer
.get_channel_data(channel_number)
.unwrap()
};
let mut channel_data = [create_channel(0), create_channel(1)];
简单、安全且最有效。
比起索引更喜欢迭代器。
如果您坚持两步初始化,那么使用迭代器而不是索引来避免差一错误。
你的情况:
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();
}
Iterator
上有很多实用函数,在这种特殊情况下,enumerate
会将iter_mut()
返回的项目(一个&mut Vec<f32>
)包装成一个元组(usize, &mut Vec<32>)
:
- 您可以直接访问元素,无需计算。
- 您还有元素的索引,没有差一错误。
我的第一个 Rust 生成的 WASM 产生了以下错误,我不知道如何进行调试。
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)
上下文:最新 Chrome,从 TypeScript 自定义元素调用的 Rust wasm-bindgen 代码,在阴影 DOM 中对 canvas 进行操作。呈现给 canvas 的数据来自 HTML5 AudioBuffer。所有 Rust 变量都在局部范围内。
如果文档中只出现一个实例,Web 组件将完美运行,但如果我有更多实例,则会像上面那样转储堆栈跟踪。代码运行没有任何其他问题。
我知道 Chrome 中存在显着的内存错误 -- 它们看起来像这样吗,或者有经验的 rust/wasm 开发人员可以告诉我这是否异常?
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"
rust 代码很小,只将 AudioBuffer 的两个通道渲染到提供的 HTMLCanvasElement:
#[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();
}
// ...
我试过注释掉功能,如果代码没有触及 canvas 但触及了上面的内容,我会收到错误消息。进行以下更改会导致一个简单的 'out of wam memory' 错误。音频文件是 1,200 k.
let channel_data: [Vec<f32>; 2] = [
audio_buffer.get_channel_data(0).unwrap(),
audio_buffer.get_channel_data(1).unwrap()
];
EDIT: 后面的out of memory
错误,对于上面正确的代码,真的把我吓坏了,但实际上是
你的问题是你创建了一大块未初始化的内存并且没有正确初始化它:
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();
}
Range
s (a.k.a. a..b
) are exclusive in Rust. This means that your loop does not iterate twice as you would suppose, but instead only once and you have one uninitialized Vec<f32>
which then will panic while dropping it. (Please see
这里有几种可能性。
- 使用适当的范围,例如
0..2
- 使用 inclusive range
0..=1
- 不要使用不安全的结构,而是
这将正确初始化两个let mut channel_data: [Vec<f32>; 2] = Default::default()
Vec
。
有关如何初始化数组的更完整概述,请参阅
作为旁注:避免使用 unsafe
,特别是如果您是 Rust 新手。
这里有两个问题:
- 您创建了一个未初始化的内存块,并将其视为已初始化。
- 你的迭代是错误的,
0..1
迭代了[0]
(它是独占的)。
让我们一次检查一个。
不要使用 unsafe
。
一般来说,你应该尽量避免unsafe
。使用它的理由很少,使用不当的方法很多(比如这里)。
问题.
在这种特殊情况下:
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in /*...*/ {
channel_data[channel_number] = /*...*/;
}
有两个问题:
- 出于安全原因,不推荐使用
std::mem::uninitialized
;使用它是一个非常糟糕的主意。它的替换是MaybeUninitialized
. - 分配给未初始化的内存是未定义的行为。
Rust 中没有赋值运算符,为了执行赋值,语言将:
- 删除上一个实例。
- 覆盖现在未使用的内存。
丢弃认为是 Vec
的原始内存是未定义的行为;在这种情况下,可能的效果是读取并释放了一些随机指针值。这可能会崩溃,这可能会释放一个不相关的指针,导致后来的崩溃或内存损坏,这是 BAD.
解决方法。
这里没有理由使用unsafe
:
- 完全可以安全地执行数组的初始化。
- 完全可以直接初始化数组
- 如果您坚持两步初始化,不执行默认初始化几乎没有性能优势,因为
Vec
的Default
实现不分配内存。
简而言之:
auto create_channel = |channel_number: u32| {
audio_buffer
.get_channel_data(channel_number)
.unwrap()
};
let mut channel_data = [create_channel(0), create_channel(1)];
简单、安全且最有效。
比起索引更喜欢迭代器。
如果您坚持两步初始化,那么使用迭代器而不是索引来避免差一错误。
你的情况:
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();
}
Iterator
上有很多实用函数,在这种特殊情况下,enumerate
会将iter_mut()
返回的项目(一个&mut Vec<f32>
)包装成一个元组(usize, &mut Vec<32>)
:
- 您可以直接访问元素,无需计算。
- 您还有元素的索引,没有差一错误。