Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[project-vvm-async-api] "buffer"をRustの世界で保持し続ける #525

193 changes: 0 additions & 193 deletions crates/voicevox_core_c_api/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::alloc::Layout;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Debug;

use const_default::ConstDefault;
Expand Down Expand Up @@ -177,194 +175,3 @@ impl ConstDefault for VoicevoxSynthesisOptions {
}
};
}

// libcのmallocで追加のアロケーションを行うことなく、`Vec<u8>`や`Vec<f32>`の内容を直接Cの世界に貸し出す。

/// Rustの世界の`Box<[impl Copy]>`をCの世界に貸し出すため、アドレスとレイアウトを管理するもの。
///
/// `Mutex`による内部可変性を持ち、すべての操作は`&self`から行うことができる。
pub(crate) struct BufferManager(Mutex<BufferManagerInner>);

struct BufferManagerInner {
address_to_layout_table: BTreeMap<usize, Layout>,
owned_str_addrs: BTreeSet<usize>,
static_str_addrs: BTreeSet<usize>,
}

impl BufferManager {
pub const fn new() -> Self {
Self(Mutex::new(BufferManagerInner {
address_to_layout_table: BTreeMap::new(),
owned_str_addrs: BTreeSet::new(),
static_str_addrs: BTreeSet::new(),
}))
}

pub fn vec_into_raw<T: Copy>(&self, vec: Vec<T>) -> (*mut T, usize) {
let BufferManagerInner {
address_to_layout_table,
..
} = &mut *self.0.lock().unwrap();

let slice = Box::leak(vec.into_boxed_slice());
let layout = Layout::for_value(slice);
let len = slice.len();
let ptr = slice.as_mut_ptr();
let addr = ptr as usize;

let not_occupied = address_to_layout_table.insert(addr, layout).is_none();

assert!(not_occupied, "すでに値が入っている状態はおかしい");

(ptr, len)
}

/// `vec_into_raw`でC API利用側に貸し出したポインタに対し、デアロケートする。
///
/// # Safety
///
/// - `buffer_ptr`は`vec_into_raw`で取得したものであること。
pub unsafe fn dealloc_slice<T: Copy>(&self, buffer_ptr: *const T) {
let BufferManagerInner {
address_to_layout_table,
..
} = &mut *self.0.lock().unwrap();

let addr = buffer_ptr as usize;
let layout = address_to_layout_table.remove(&addr).expect(
"解放しようとしたポインタはvoicevox_coreの管理下にありません。\
誤ったポインタであるか、二重解放になっていることが考えられます",
);

if layout.size() > 0 {
// `T: Copy`より、`T: !Drop`であるため`drop_in_place`は不要

// SAFETY:
// - `addr`と`layout`は対応したものである
// - `layout.size() > 0`より、`addr`はダングリングではない有効なポインタである
std::alloc::dealloc(addr as *mut u8, layout);
}
}

pub fn c_string_into_raw(&self, s: CString) -> *mut c_char {
let BufferManagerInner {
owned_str_addrs, ..
} = &mut *self.0.lock().unwrap();

let ptr = s.into_raw();
owned_str_addrs.insert(ptr as _);
ptr
}

/// `c_string_into_raw`でC API利用側に貸し出したポインタに対し、デアロケートする。
///
/// # Safety
///
/// - `ptr`は`c_string_into_raw`で取得したものであること。
pub unsafe fn dealloc_c_string(&self, ptr: *mut c_char) {
let BufferManagerInner {
owned_str_addrs,
static_str_addrs,
..
} = &mut *self.0.lock().unwrap();

if !owned_str_addrs.remove(&(ptr as _)) {
if static_str_addrs.contains(&(ptr as _)) {
panic!(
"解放しようとしたポインタはvoicevox_core管理下のものですが、\
voicevox_coreがアンロードされるまで永続する文字列に対するものです。\
解放することはできません",
)
}
panic!(
"解放しようとしたポインタはvoicevox_coreの管理下にありません。\
誤ったポインタであるか、二重解放になっていることが考えられます",
);
}
drop(CString::from_raw(ptr));
}

pub fn memorize_static_str(&self, ptr: *const c_char) -> *const c_char {
let BufferManagerInner {
static_str_addrs, ..
} = &mut *self.0.lock().unwrap();

static_str_addrs.insert(ptr as _);
ptr
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn buffer_manager_works() {
let mut buffer_manager = BufferManager::new();

rent_and_dealloc(&mut buffer_manager, vec::<()>(0, &[]));
rent_and_dealloc(&mut buffer_manager, vec(0, &[()]));
rent_and_dealloc(&mut buffer_manager, vec(2, &[()]));

rent_and_dealloc(&mut buffer_manager, vec::<u8>(0, &[]));
rent_and_dealloc(&mut buffer_manager, vec(0, &[0u8]));
rent_and_dealloc(&mut buffer_manager, vec(2, &[0u8]));

rent_and_dealloc(&mut buffer_manager, vec::<f32>(0, &[]));
rent_and_dealloc(&mut buffer_manager, vec(0, &[0f32]));
rent_and_dealloc(&mut buffer_manager, vec(2, &[0f32]));

fn rent_and_dealloc(buffer_manager: &mut BufferManager, vec: Vec<impl Copy>) {
let expected_len = vec.len();
let (ptr, len) = buffer_manager.vec_into_raw(vec);
assert_eq!(expected_len, len);
unsafe {
buffer_manager.dealloc_slice(ptr);
}
}

fn vec<T: Copy>(initial_cap: usize, elems: &[T]) -> Vec<T> {
let mut vec = Vec::with_capacity(initial_cap);
vec.extend_from_slice(elems);
vec
}
}

#[test]
#[should_panic(
expected = "解放しようとしたポインタはvoicevox_coreの管理下にありません。誤ったポインタであるか、二重解放になっていることが考えられます"
)]
fn buffer_manager_denies_unknown_slice_ptr() {
let buffer_manager = BufferManager::new();
unsafe {
let x = 42;
buffer_manager.dealloc_slice(&x as *const i32);
}
}

#[test]
#[should_panic(
expected = "解放しようとしたポインタはvoicevox_coreの管理下にありません。誤ったポインタであるか、二重解放になっていることが考えられます"
)]
fn buffer_manager_denies_unknown_char_ptr() {
let buffer_manager = BufferManager::new();
unsafe {
let s = CStr::from_bytes_with_nul(b"\0").unwrap().to_owned();
buffer_manager.dealloc_c_string(s.into_raw());
}
}

#[test]
#[should_panic(
expected = "解放しようとしたポインタはvoicevox_core管理下のものですが、voicevox_coreがアンロードされるまで永続する文字列に対するものです。解放することはできません"
)]
fn buffer_manager_denies_known_static_char_ptr() {
let buffer_manager = BufferManager::new();
unsafe {
buffer_manager.memorize_static_str(STATIC.as_ptr() as _);
buffer_manager.dealloc_c_string(STATIC.as_ptr() as *mut c_char);
}

static STATIC: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
}
}
40 changes: 19 additions & 21 deletions crates/voicevox_core_c_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ mod c_impls;
/// cbindgen:ignore
mod compatible_engine;
mod helpers;
mod owners;
use self::helpers::*;
use self::owners::{C_STRING_OWNER, U8_SLICE_OWNER};
use chrono::SecondsFormat;
use const_default::ConstDefault;
use derive_getters::Getters;
Expand Down Expand Up @@ -65,9 +67,6 @@ static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
Runtime::new().unwrap()
});

// C_APIに渡すために,VecやCStringのサイズを記憶しながら生ポインタを得るためのマネージャ
static BUFFER_MANAGER: BufferManager = BufferManager::new();

/*
* Cの関数として公開するための型や関数を定義するこれらの実装はvoicevox_core/publish.rsに定義してある対応する関数にある
* この関数ではvoicevox_core/publish.rsにある対応する関数の呼び出しと、その戻り値をCの形式に変換する処理のみとする
Expand Down Expand Up @@ -356,7 +355,7 @@ pub extern "C" fn voicevox_create_supported_devices_json(
into_result_code_with_error((|| {
let supported_devices =
CString::new(SupportedDevices::create()?.to_json().to_string()).unwrap();
output_supported_devices_json.write(BUFFER_MANAGER.c_string_into_raw(supported_devices));
C_STRING_OWNER.own_and_lend(supported_devices, output_supported_devices_json);
Ok(())
})())
}
Expand Down Expand Up @@ -401,7 +400,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_audio_query(
))?;
let audio_query = CString::new(audio_query_model_to_json(&audio_query))
.expect("should not contain '\\0'");
output_audio_query_json.write(BUFFER_MANAGER.c_string_into_raw(audio_query));
C_STRING_OWNER.own_and_lend(audio_query, output_audio_query_json);
Ok(())
})())
}
Expand Down Expand Up @@ -444,7 +443,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases(
))?;
let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases))
.expect("should not contain '\\0'");
output_accent_phrases_json.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases));
C_STRING_OWNER.own_and_lend(accent_phrases, output_accent_phrases_json);
Ok(())
})())
}
Expand Down Expand Up @@ -476,7 +475,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_data(
)?;
let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases))
.expect("should not contain '\\0'");
output_accent_phrases_json.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases));
C_STRING_OWNER.own_and_lend(accent_phrases, output_accent_phrases_json);
Ok(())
})())
}
Expand Down Expand Up @@ -508,7 +507,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_phoneme_length(
)?;
let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases))
.expect("should not contain '\\0'");
output_accent_phrases_json.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases));
C_STRING_OWNER.own_and_lend(accent_phrases, output_accent_phrases_json);
Ok(())
})())
}
Expand Down Expand Up @@ -540,7 +539,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_replace_mora_pitch(
)?;
let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases))
.expect("should not contain '\\0'");
output_accent_phrases_json.write(BUFFER_MANAGER.c_string_into_raw(accent_phrases));
C_STRING_OWNER.own_and_lend(accent_phrases, output_accent_phrases_json);
Ok(())
})())
}
Expand Down Expand Up @@ -588,9 +587,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_synthesis(
StyleId::new(style_id),
&SynthesisOptions::from(options),
))?;
let (ptr, len) = BUFFER_MANAGER.vec_into_raw(wav);
output_wav.write(ptr);
output_wav_length.write(len);
U8_SLICE_OWNER.own_and_lend(wav, output_wav, output_wav_length);
Ok(())
})())
}
Expand Down Expand Up @@ -636,9 +633,7 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts(
StyleId::new(style_id),
&TtsOptions::from(options),
))?;
let (ptr, size) = BUFFER_MANAGER.vec_into_raw(output);
output_wav.write(ptr);
output_wav_length.write(size);
U8_SLICE_OWNER.own_and_lend(output, output_wav, output_wav_length);
Ok(())
})())
}
Expand All @@ -649,8 +644,8 @@ pub unsafe extern "C" fn voicevox_synthesizer_tts(
/// # Safety
/// @param voicevox_audio_query で確保されたポインタであり、かつ呼び出し側でバッファの変更を行われていないこと
#[no_mangle]
pub unsafe extern "C" fn voicevox_json_free(json: *mut c_char) {
BUFFER_MANAGER.dealloc_c_string(json);
pub extern "C" fn voicevox_json_free(json: *mut c_char) {
C_STRING_OWNER.delete(json);
}

/// wav データのメモリを解放する
Expand All @@ -659,8 +654,8 @@ pub unsafe extern "C" fn voicevox_json_free(json: *mut c_char) {
/// # Safety
/// @param wav voicevox_tts,voicevox_synthesis で確保されたポインタであり、かつ呼び出し側でバッファの変更を行われていないこと
#[no_mangle]
pub unsafe extern "C" fn voicevox_wav_free(wav: *mut u8) {
BUFFER_MANAGER.dealloc_slice(wav);
pub extern "C" fn voicevox_wav_free(wav: *mut u8) {
U8_SLICE_OWNER.delete(wav);
}

/// エラー結果をメッセージに変換する
Expand All @@ -670,8 +665,11 @@ pub unsafe extern "C" fn voicevox_wav_free(wav: *mut u8) {
pub extern "C" fn voicevox_error_result_to_message(
result_code: VoicevoxResultCode,
) -> *const c_char {
BUFFER_MANAGER.memorize_static_str(
voicevox_core::result_code::error_result_to_message(result_code).as_ptr() as *const c_char,
C_STRING_OWNER.memorize_static(
CStr::from_bytes_with_nul(
voicevox_core::result_code::error_result_to_message(result_code).as_ref(),
)
.unwrap(),
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved
)
}

Expand Down
8 changes: 8 additions & 0 deletions crates/voicevox_core_c_api/src/owners.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! `Box<[u8]>`や`CString`といったバッファの所有者(owner)。
//!
//! libcのmallocで追加のアロケーションを行うことなく、バッファを直接Cの世界に貸し出すことができる。

mod c_string;
mod slice;

pub(crate) use self::{c_string::C_STRING_OWNER, slice::U8_SLICE_OWNER};
Loading