Skip to content

Commit 872369a

Browse files
committed
docs(modules): 接口文档同步 (V3 WP1-WP8 + D-31~D-46)
合并 2 个文档同步提交(d326924..d75ae90): - ds-session/g2p/c-abi/ds-bank/overview: V3 重构接口文档同步 - core/ds-infer: bug-fix 接口文档同步 (D-40/D-41/D-45/D-46) - 删除 docs/refactoring-v3/README.md (规划已落地为 docs/modules/)
1 parent 14a1521 commit 872369a

8 files changed

Lines changed: 551 additions & 225 deletions

File tree

‎docs/modules/c-abi.md‎

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,26 @@ void srt_free_string_array(char **arr, size_t count);
5757
5858
`srt_last_error()` 返回 `[Category::Code] message\n at file:line:function` 格式的完整错误描述(不仅是纯消息)。`srt_last_error_code()` 返回映射后的 `srt_error` 枚举值。
5959
60+
### setLastError 双通道错误码 (D-44)
61+
62+
`setLastError` 提供单参/双参两个重载:
63+
64+
```cpp
65+
// lib/C/LastError.h
66+
namespace srt::c::detail {
67+
void setLastError(std::string message); // 旧:code 固定 SRT_ERR_GENERIC
68+
void setLastError(std::string message, srt_error code); // D-44:显式 code
69+
void setLastError(const srt::core::Error &error); // 从 Error 提取 toString + mapError
70+
}
71+
```
72+
73+
D-44 修复前,`srt_v4.cpp` 中 13 处调用单参数 `setLastError(message)`,TLS 错误码固定为 `SRT_ERR_GENERIC`,但函数实际返回 `SRT_ERR_INVALID_ARG` 或 `SRT_ERR_OUT_OF_MEM`,违反 ROBUST-05 双通道错误报告契约。修复后 13 处全部改为双参数:
74+
75+
- 10 处 `SRT_ERR_INVALID_ARG`(null session/handle、null paths/roots/phonemes 数组、null entry、null packageId/singerId)
76+
- 3 处 `SRT_ERR_OUT_OF_MEM`(`new(std::nothrow)` 返回 nullptr 的 create 函数)
77+
78+
返回值与 TLS 错误码现在保证一致。
79+
6080
### 错误码
6181

6282
```c
@@ -166,11 +186,13 @@ C 调用方 (Python/Rust/C#)
166186

167187
`unittests/C/test_c_abi.cpp` — C ABI 单元测试,覆盖 `srt_last_error`/`srt_last_error_code` 生命周期、错误码映射、BF-25/BF-29/BF-30 回归。
168188

189+
`unittests/C/test_c_abi_input_validation.cpp` — D-44 修复前因零大小数组(C2466)和按值实例化不透明类型(C2079)在 MSVC 上无法编译,掩盖了 3 个运行时测试失败。D-44 改用 `{"/tmp"}` + `count=0` 触发 INVALID_ARG 路径、`reinterpret_cast<...>(0xDEADBEEF)` 从整数构造指针值触发 INVALID_HANDLE 路径,恢复编译并暴露真实测试结果。修复后 `synthrt-unittest-c` 63 cases / 144 assertions 全通过。
190+
169191
---
170192

171-
## vnext: session/model/task 句柄 API (WP7 + WP8)
193+
## vnext: session/model/task 句柄 API (WP6 + WP7 + WP8)
172194

173-
参考 `docs/refactoring-vnext/04-diagnostics-degradation-and-migration.md` "最小 C ABI"。vnext 在原 ABI 之上追加(不替换)三种新句柄:`srt_SessionHandle`、`srt_ModelHandle`、`srt_TaskHandle`,分别绑定 `ds::session::VoicebankSession`、`ds::session::ModelSetHandle` 与 `shared_future<RefreshResult>`。
195+
参考 `docs/refactoring-vnext/04-diagnostics-degradation-and-migration.md` "最小 C ABI"。vnext 在原 ABI 之上追加(不替换)三种新句柄:`srt_SessionHandle`、`srt_ModelHandle`、`srt_TaskHandle`,分别绑定 `ds::session::VoicebankSession`、`ds::session::ModelSetHandle` 与 `shared_future<RefreshResult>`。WP6 又新增 `srt_RuntimeHandle` / `srt_LanguageServiceHandle` 两种资源句柄,支持 `srt_session_create_with_resources` 资源注入构造(D-27/K-01)。
174196

175197
### 句柄类型与任务状态
176198

@@ -180,6 +202,10 @@ typedef struct srt_SessionHandle srt_SessionHandle;
180202
typedef struct srt_ModelHandle srt_ModelHandle;
181203
typedef struct srt_TaskHandle srt_TaskHandle;
182204

205+
// v3 / WP6:借入式资源句柄。caller 拥有,session 通过 create_with_resources 借入。
206+
typedef struct srt_RuntimeHandle srt_RuntimeHandle;
207+
typedef struct srt_LanguageServiceHandle srt_LanguageServiceHandle;
208+
183209
typedef enum {
184210
SRT_TASK_STATE_PENDING = 0,
185211
SRT_TASK_STATE_RUNNING = 1,
@@ -189,10 +215,10 @@ typedef enum {
189215
} srt_TaskState;
190216
```
191217

192-
### Session / Model / Task API(14 个 vnext 函数)
218+
### Session / Model / Task API(18 个 vnext 函数:14 WP7+WP8 + 4 WP6)
193219

194220
```c
195-
// Session
221+
// Session(WP7)
196222
srt_SessionHandle *srt_session_create_v2(void);
197223
srt_error srt_session_destroy_v2(srt_SessionHandle *handle);
198224
srt_error srt_session_set_roots(srt_SessionHandle *handle,
@@ -202,24 +228,45 @@ srt_error srt_session_set_reserved_phonemes(srt_SessionHandle *handle,
202228
srt_TaskHandle *srt_session_refresh_async(srt_SessionHandle *handle);
203229
const void *srt_session_snapshot(srt_SessionHandle *handle);
204230

205-
// Model
231+
// Session with resources(WP6 / D-27)
232+
srt_SessionHandle *srt_session_create_with_resources(
233+
srt_RuntimeHandle *runtime,
234+
srt_LanguageServiceHandle *languageService);
235+
236+
// Resource handles(WP6)
237+
srt_RuntimeHandle *srt_runtime_create(void);
238+
srt_error srt_runtime_destroy(srt_RuntimeHandle *handle);
239+
srt_LanguageServiceHandle *srt_language_service_create(void);
240+
srt_error srt_language_service_destroy(srt_LanguageServiceHandle *handle);
241+
242+
// Model(WP7)
206243
srt_ModelHandle *srt_model_create(srt_SessionHandle *session,
207244
const char *packageId,
208245
const char *singerId,
209246
const char *version);
210247
srt_error srt_model_destroy(srt_ModelHandle *handle);
211248

212-
// Task
249+
// Task(WP7)
213250
srt_TaskState srt_task_state(srt_TaskHandle *handle);
214251
srt_error srt_task_wait(srt_TaskHandle *handle, int timeout_ms);
215252
srt_error srt_task_cancel(srt_TaskHandle *handle);
216253
void srt_task_destroy(srt_TaskHandle *handle);
217254
const char *srt_task_result_json(srt_TaskHandle *handle, size_t *out_size);
218255

219-
// Buffer
256+
// Buffer(WP7)
220257
void srt_free_buffer(void *ptr);
221258
```
222259
260+
### WP6: 资源注入式 session (D-27 / D-36)
261+
262+
`srt_session_create_with_resources(runtime, languageService)` 等价于 C++ 的 `VoicebankSession(SessionResources{...})`。两个 handle 必须非空且必须由调用方保活到 session 销毁之后:
263+
264+
- `runtime` / `languageService` 任一为 NULL → 返回 NULL,`srt_last_error` 设为 `InvalidArgument`。
265+
- session 通过非拥有 aliasing `shared_ptr` 借用底层对象,不延长其生命周期。
266+
- 默认构造的 `srt_session_create_v2()` 仅走 discovery 路径(D-36/K-10),调用 `convertG2p` / `createModelSet` 会返回 `G2pNotImplementedError` / `InferenceNotInitialized`。
267+
268+
资源句柄自己管理 `Runtime` / `LanguageService` 实例的生命周期(`create` / `destroy` 显式配对),可被多个 session 共享借用。`destroy` 是幂等的:销毁后同一指针值仍可传回库,decode 为缺失表项返回 `SRT_ERR_INVALID_HANDLE`(与 session/model/task 一致)。
269+
223270
### 函数语义与错误路径
224271
225272
| 函数 | 语义 | 成功返回 | 失败路径 |

‎docs/modules/core.md‎

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,39 @@ public:
5353
3. `loadPackage()` — 加载声库包,创建 spec
5454
4. `initialize()` — 一次性初始化(当前为 no-op Stage 2)
5555
56+
### Runtime::loadPackage 部分失败回滚 (D-40)
57+
58+
`loadPackage` 内部按 `inferences → singers` 顺序解析每个 `ModuleSpec`,每个 spec 由 `parseSpec` 构造、`loadSpec(Initialized)` → `loadSpec(Ready)` 推进。D-40 修复前若第 N 个 spec 失败,前 N-1 个已 `loadSpec(Ready)` 的 spec 残留在 `ModuleCategory::Impl::modules` 列表(raw pointer,`std::list<ModuleSpec *>`);重试同一包时 `loadSpecBase(Initialized)` 的 duplicate-detection 会对残留 spec 触发 `PackageDuplicate`,用户必须重启进程才能恢复。附带 bug:duplicate-detection 错误路径上 `parseSpec` 已构造但未 `loadSpec(Deleted)` 的 spec 未被 `delete`,造成内存泄漏。
59+
60+
D-40 修复方案(commit 93c0c92):
61+
62+
```cpp
63+
struct CommittedSpec { ModuleCategory *cat; ModuleSpec *spec; };
64+
std::vector<CommittedSpec> committed;
65+
auto rollbackCommitted = [&committed]() {
66+
for (auto it = committed.rbegin(); it != committed.rend(); ++it) {
67+
(void) it->cat->loadSpec(it->spec, ModuleSpec::Deleted);
68+
delete it->spec;
69+
}
70+
committed.clear();
71+
};
72+
73+
for (const auto &ref : inferenceRefs) {
74+
auto spec = std::make_unique<InferenceSpec>(...); // pending 由 unique_ptr 持有
75+
auto loadExp = infCat->loadSpec(spec.get(), ModuleSpec::Initialized);
76+
if (!loadExp) { rollbackCommitted(); return loadExp.takeError(); }
77+
loadExp = infCat->loadSpec(spec.get(), ModuleSpec::Ready);
78+
if (!loadExp) {
79+
infCat->loadSpec(spec.get(), ModuleSpec::Deleted); // 移除 spec
80+
return loadExp.takeError(); // unique_ptr 析构释放
81+
}
82+
committed.push_back({infCat, spec.get()});
83+
spec.release(); // 成功:移交所有权给 category
84+
}
85+
```
86+
87+
所有失败路径统一调用 `rollbackCommitted()` 逆序回滚,符合 ROBUST-05(禁止隐式错误吞没)、ROBUST-01(Expected 传播)、D-11(公共签名不变)、ARCH-02(未新增错误码)。
88+
5689
### ITask
5790

5891
```cpp
@@ -161,14 +194,39 @@ enum class ErrorCode {
161194
SvsSingerNotFound = 600, ..., SvsCategoryNotFound, // SVS (600-699)
162195
};
163196
164-
enum class ErrorCategory { None, General, Package, Inference, G2P, Driver, S2P, SVS };
197+
enum class ErrorCategory { None, General, Package, Inference, G2P, Driver, S2P, SVS, Audio, Extract };
165198
166199
ErrorCategory errorCodeCategory(ErrorCode code) noexcept;
167200
const char *errorCodeToString(ErrorCode code) noexcept; // "Inference::ModelLoadFailed"
168201
```
169202

203+
V3-09/V3-10 在 Inference 段追加 `LoadFailed` (217) / `RuntimePackageNotLoaded` (218);在 G2P 段追加 `G2pVersionAmbiguous` (321);V3-21 在 SVS 段追加 `SvsSingerAmbiguous` (601)。`Audio` (700-799) / `Extract` (800-899) 是 v4 新增的两个段。`ErrorCategory` 也对应追加 `Audio` / `Extract`,`errorCodeCategory` 使用范围检查(如 `>=600` => SVS),无需修改。
204+
170205
枚举值只追加不重排(ARCH-02),保证 ABI 稳定性。
171206

207+
### LevelCompatibilityChecker::ValidationResult 嵌套类型导出 (D-45)
208+
209+
```cpp
210+
// include/synthrt/Core/Dependency/LevelCompatibilityChecker.h
211+
class SRT_CORE_EXPORT LevelCompatibilityChecker {
212+
public:
213+
struct LevelConfig { ... };
214+
215+
// D-45:嵌套 struct 必须独立标记 SRT_CORE_EXPORT
216+
struct SRT_CORE_EXPORT ValidationResult {
217+
bool isCompatible;
218+
int pluginLevel, systemMinimum, systemMaximum;
219+
std::string message, suggestion;
220+
bool isInSupportedRange() const;
221+
};
222+
223+
static ValidationResult checkCorePlugin(int pluginLevel, const LevelConfig &config);
224+
// ...
225+
};
226+
```
227+
228+
MSVC 的 `dllexport` 语义与 GCC/Clang visibility 不同:外层类标记 `SRT_CORE_EXPORT` 只导出外层类自身的成员,**不传播到嵌套类型的成员**(GCC/Clang 的 visibility 会传播)。D-45 修复前 `test_dependency_graph_extreme.cpp` 链接失败:LNK2019 未解析 `LevelCompatibilityChecker::ValidationResult::isInSupportedRange`。修复方案是给嵌套 struct 显式添加 `SRT_CORE_EXPORT`(commit c5e14a0)。这一约束对 GCC/Clang 是 no-op(已可见的类型再标记 visibility 属性不会改变行为),但对 MSVC 是强制的。所有跨 DLL 边界使用的嵌套类型必须显式标记 `SRT_CORE_EXPORT`(INFRA-01)。
229+
172230
### Error 类
173231
174232
```cpp

‎docs/modules/ds-bank.md‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,30 @@ public:
5050
const std::string &packageId,
5151
const std::string &version) const;
5252

53-
// 获取 packageId 对应的包目录
53+
// 获取 packageId 对应的包目录(已弃用,仅返回首个匹配)
54+
[[deprecated("Use packageDirectories(packageId). Will be removed in Level=3.")]]
5455
std::filesystem::path packageDirectory(const std::string &packageId) const;
56+
57+
// V3-01 §1.6:返回 (version, path) 列表,多版本同 packageId 全部保留
58+
std::vector<PackageDirectoryResult> packageDirectories(
59+
const std::string &packageId) const;
5560
};
5661
```
5762
63+
### PackageDirectoryResult (V3-01)
64+
65+
```cpp
66+
// include/diffsinger/Bank/VoicebankScanner.h
67+
namespace ds::bank;
68+
69+
struct PackageDirectoryResult {
70+
stdc::VersionNumber version;
71+
std::filesystem::path path;
72+
};
73+
```
74+
75+
`packageDirectories(packageId)` 返回所有匹配条目,按发现顺序排列;`packageId` 未知时返回空 vector。这是 `packageDirectory(packageId)` 的版本感知替代——后者只返回首个匹配,多版本同 packageId 会合并到一条,调用方需迁移到 `packageDirectories` 以获得完整版本隔离(V3-01 第 5 层)。`packageDirectory` 已标记 `[[deprecated]]`,将在 Level=3 移除。
76+
5877
### SingerRef
5978

6079
```cpp

‎docs/modules/ds-infer.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ public:
7979
};
8080
```
8181
82-
**注意**: `SingerSpec` 通过 `packageId()`/`packageVersion()` 暴露包标识(由 `Runtime::loadPackage` 从 `desc.json` 注入)。多候选消歧按 `spec->packageId()` 精确字符串匹配(BF-23 v2 修复,不再依赖路径目录名)。`SingerImport` 支持显式跨包声明:import JSON 中声明 `"package"` + `"version"` 时跨包解析(ARCH-06),version 支持 `"*"` 通配或精确匹配;未声明时严格同包隔离。错误码使用 `ErrorCode::SvsSingerNotFound`/`SvsStageResolveFailed`,重复加载使用 `ErrorCode::PackageDuplicate`。
82+
**注意**: `SingerSpec` 通过 `packageId()`/`packageVersion()` 暴露包标识(由 `Runtime::loadPackage` 从 `desc.json` 注入)。多候选消歧按 `spec->packageId()` 精确字符串匹配(BF-23 v2 修复,不再依赖路径目录名)。`SingerImport` 支持显式跨包声明:import JSON 中声明 `"package"` + `"version"` 时跨包解析(ARCH-06),version 支持 `"*"` 通配或精确匹配;未声明时严格同包隔离。
83+
84+
**多版本歧义错误码 (D-41)**: 当 `(packageId, singerId, version)` 元组匹配多个 `SingerSpec` 时返回 `ErrorCode::SvsSingerAmbiguous`(V3-21,601),不再使用 `SvsSingerNotFound`。镜像 G2P 侧 `G2pVersionAmbiguous` (321),保持两侧语义对称。错误创建使用 `Error::inferenceError()`,包含 `singerId` 上下文。重复加载使用 `ErrorCode::PackageDuplicate`,stage 缺失使用 `InferenceStageMissing`,未找到使用 `SvsSingerNotFound`。
85+
86+
**D-41 与 D-42 的关系**: D-41 修复 `SingerStageResolver`(stage 层)的歧义错误码语义;D-42 修复 `VoicebankSession::findSinger`(snapshot 层)的静默选择(见 [ds-session.md](ds-session.md#findsinger-多版本歧义拒绝-d-42))。两者共同构成多版本歧义的两层防护:snapshot 层先拒绝、stage 层兜底。
8387
8488
### ModelSet
8589
@@ -254,6 +258,7 @@ Catch2 单元测试位于 `domains/ds-infer/unittests/catch2/`,覆盖 Algorith
254258
| BF-40 | `OnnxTensor::createFromRawView` 在 `std::memcpy` 前未校验 `data.size() == tensor->_bytesSize`:`data.size() > _bytesSize` 导致堆缓冲区溢出(OOB 写),`data.size() < _bytesSize` 导致张量缓冲区部分未初始化。修复:在拷贝前增加显式大小匹配校验,不匹配时返回 `InvalidArgument` 错误(包含两边字节数) |
255259
| BF-41 | `preprocessPhonemeDurations`(InputWord.cpp)和 `preprocessLinguisticWord`(LinguisticEncoder.cpp)除以 `frameWidth` 时无本地校验。四个推理插件(Duration/Pitch/Variance/Acoustic)在调用前已校验 `frameWidth > 0`,但工具函数本身无防御性检查(defense-in-depth)。修复:在两个函数入口增加 `std::isfinite && > 0` 校验,返回 `InvalidArgument`,与插件侧模式一致 |
256260
| BF-42 | `resample()`/`interpolate()`(Algorithm.h)三处崩溃与静默错误:(1) `interpolate()` 在 `referencePoints.front()/back()` 前未校验非空,空引用数组触发 UB;(2) `resample()` timestep 校验为 `== 0` 漏掉负值,负 timestep 使 `arange()` 返回空 → `interpolate()` 返回空 → `targetSamples.back()` 在空 vector 上崩溃;(3) `preprocessSpeakerEmbeddingFrames` 未校验 `frameWidth > 0`,且 BF-34 的 `interval == 0` 校验漏掉负值,导致 `resample()` 返回空后 speaker 被静默跳过(ROBUST-05)。修复:`interpolate()` 增加空引用数组早返回;`resample()` timestep 校验改为 `<= 0`,并在 `.back()` 前增加 `!empty()` 防御;`preprocessSpeakerEmbeddingFrames` 入口校验 `frameWidth > 0`,BF-34 interval 校验改为 `<= 0` |
261+
| D-46 | `Algorithm.h::arange()` 对 NaN/inf 输入无防护——`static_cast<size_t>(std::ceil(NaN))` 是 UB(MSVC x64 上通常为 0 或 SIZE_MAX),`ceil(inf)` 产生 inf 同样 UB;subnormal step 使 `(stop-start)/step` 极大,`reserve()` 抛 `bad_alloc`。`resample()` 的 `timestep <= 0` 检查无法拦截 NaN(`NaN <= 0` 为 false),inf 会触发 `arange(0, inf, targetTimestep)` 的巨大分配。修复:`arange()` 添加 `!std::isfinite` 检查 + 100M 大小上限;`resample()` 添加 `!std::isfinite(timestep/targetTimestep)` 前置检查。同时修正 9 个测试错误期望(arange 负步长对称性、fillRestMidi 紧邻值填充、preprocessPhonemeDurations 帧分配、DynamicMix 线性插值、DictionaryS2P 空格容忍)以对齐实现实际行为 |
257262

258263
## Speaker Embedding Inline API
259264

0 commit comments

Comments
 (0)