lz4rip 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/Cargo.lock +28 -64
- data/Cargo.toml +0 -1
- data/README.md +12 -34
- data/ext/lz4rip/Cargo.toml +5 -5
- data/ext/lz4rip/build.rs +23 -0
- data/ext/lz4rip/src/lib.rs +542 -208
- data/ext/lz4rip/src/rb.rs +680 -0
- data/lib/lz4rip/block_codec.rb +39 -0
- data/lib/lz4rip/dict_trainer.rb +32 -0
- data/lib/lz4rip/dictionary.rb +10 -0
- data/lib/lz4rip/frame_codec.rb +46 -0
- data/lib/lz4rip/version.rb +1 -1
- data/lib/lz4rip.rb +17 -0
- metadata +14 -5
data/ext/lz4rip/src/lib.rs
CHANGED
|
@@ -1,117 +1,344 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
use std::cell::RefCell;
|
|
1
|
+
mod rb;
|
|
2
|
+
|
|
3
|
+
use std::ffi::c_void;
|
|
6
4
|
use std::io::{Cursor, Read, Write};
|
|
7
|
-
use std::
|
|
5
|
+
use std::panic::{catch_unwind, AssertUnwindSafe};
|
|
6
|
+
use std::sync::{Mutex, OnceLock, TryLockError};
|
|
8
7
|
|
|
9
8
|
use lz4::block::{self, Decompressor, DictCompressor, DictTrainer};
|
|
10
|
-
use lz4::frame::{BlockMode, FrameDecoder, FrameEncoder, FrameInfo};
|
|
9
|
+
use lz4::frame::{BlockMode, FrameDecoder, FrameDecoderOptions, FrameEncoder, FrameInfo};
|
|
10
|
+
use rb_sys::{rb_data_type_struct__bindgen_ty_1, rb_data_type_t, size_t, VALUE};
|
|
11
|
+
|
|
12
|
+
use crate::rb::{RbResult, RubyErr};
|
|
11
13
|
|
|
12
14
|
const COMPRESSOR_HEAP_SIZE: usize = 8192;
|
|
13
15
|
|
|
14
16
|
const LZ4_FRAME_MAGIC: [u8; 4] = [0x04, 0x22, 0x4d, 0x18];
|
|
17
|
+
const GVL_COMPRESS_THRESHOLD: usize = 256 * 1024;
|
|
18
|
+
const GVL_FRAME_DECOMPRESS_THRESHOLD: usize = 256 * 1024;
|
|
19
|
+
|
|
20
|
+
static DECOMPRESS_ERROR: OnceLock<GlobalValue> = OnceLock::new();
|
|
21
|
+
|
|
22
|
+
#[derive(Copy, Clone)]
|
|
23
|
+
struct GlobalValue(VALUE);
|
|
24
|
+
|
|
25
|
+
unsafe impl Send for GlobalValue {}
|
|
26
|
+
unsafe impl Sync for GlobalValue {}
|
|
27
|
+
|
|
28
|
+
fn decompress_error() -> VALUE {
|
|
29
|
+
DECOMPRESS_ERROR
|
|
30
|
+
.get()
|
|
31
|
+
.expect("DecompressError not initialized")
|
|
32
|
+
.0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn should_release_compress_gvl(input_len: usize) -> bool {
|
|
36
|
+
input_len >= GVL_COMPRESS_THRESHOLD
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn should_release_frame_decompress_gvl(input_len: usize) -> bool {
|
|
40
|
+
input_len >= GVL_FRAME_DECOMPRESS_THRESHOLD
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn with_mutex<T, R, F>(
|
|
44
|
+
mutex: &Mutex<T>,
|
|
45
|
+
release_gvl: bool,
|
|
46
|
+
name: &str,
|
|
47
|
+
input: &mut rb::InputBytes,
|
|
48
|
+
func: F,
|
|
49
|
+
) -> RbResult<R>
|
|
50
|
+
where
|
|
51
|
+
F: FnOnce(&mut T, &[u8]) -> RbResult<R>,
|
|
52
|
+
{
|
|
53
|
+
if release_gvl {
|
|
54
|
+
input.lock_for_without_gvl(true)?;
|
|
55
|
+
return rb::maybe_without_gvl(true, || {
|
|
56
|
+
let mut guard = mutex
|
|
57
|
+
.lock()
|
|
58
|
+
.map_err(|_| RubyErr::runtime(format!("{name} mutex poisoned")))?;
|
|
59
|
+
func(&mut guard, input.as_slice())
|
|
60
|
+
})?;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
match mutex.try_lock() {
|
|
64
|
+
Ok(mut guard) => func(&mut guard, input.as_slice()),
|
|
65
|
+
Err(TryLockError::WouldBlock) => {
|
|
66
|
+
// Even a small input needs protection when contention releases GVL.
|
|
67
|
+
input.lock_for_without_gvl(true)?;
|
|
68
|
+
rb::maybe_without_gvl(true, || {
|
|
69
|
+
let mut guard = mutex
|
|
70
|
+
.lock()
|
|
71
|
+
.map_err(|_| RubyErr::runtime(format!("{name} mutex poisoned")))?;
|
|
72
|
+
func(&mut guard, input.as_slice())
|
|
73
|
+
})?
|
|
74
|
+
}
|
|
75
|
+
Err(TryLockError::Poisoned(_)) => Err(RubyErr::runtime(format!("{name} mutex poisoned"))),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------- typed data ----------
|
|
80
|
+
|
|
81
|
+
struct NativeDataType(rb_data_type_t);
|
|
82
|
+
|
|
83
|
+
unsafe impl Send for NativeDataType {}
|
|
84
|
+
unsafe impl Sync for NativeDataType {}
|
|
85
|
+
|
|
86
|
+
static BLOCK_CODEC_DATA_TYPE: OnceLock<NativeDataType> = OnceLock::new();
|
|
87
|
+
static FRAME_CODEC_DATA_TYPE: OnceLock<NativeDataType> = OnceLock::new();
|
|
88
|
+
static DICT_TRAINER_DATA_TYPE: OnceLock<NativeDataType> = OnceLock::new();
|
|
89
|
+
|
|
90
|
+
fn block_codec_data_type() -> *const rb_data_type_t {
|
|
91
|
+
&BLOCK_CODEC_DATA_TYPE
|
|
92
|
+
.get_or_init(|| NativeDataType(make_block_codec_data_type()))
|
|
93
|
+
.0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn frame_codec_data_type() -> *const rb_data_type_t {
|
|
97
|
+
&FRAME_CODEC_DATA_TYPE
|
|
98
|
+
.get_or_init(|| NativeDataType(make_frame_codec_data_type()))
|
|
99
|
+
.0
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn dict_trainer_data_type() -> *const rb_data_type_t {
|
|
103
|
+
&DICT_TRAINER_DATA_TYPE
|
|
104
|
+
.get_or_init(|| NativeDataType(make_dict_trainer_data_type()))
|
|
105
|
+
.0
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn make_block_codec_data_type() -> rb_data_type_t {
|
|
109
|
+
rb_data_type_t {
|
|
110
|
+
wrap_struct_name: c"lz4rip_block_codec".as_ptr(),
|
|
111
|
+
function: rb_data_type_struct__bindgen_ty_1 {
|
|
112
|
+
dmark: None,
|
|
113
|
+
dfree: Some(block_codec_free),
|
|
114
|
+
dsize: Some(block_codec_native_size),
|
|
115
|
+
dcompact: None,
|
|
116
|
+
reserved: [std::ptr::null_mut(); 1],
|
|
117
|
+
},
|
|
118
|
+
parent: std::ptr::null(),
|
|
119
|
+
data: std::ptr::null_mut(),
|
|
120
|
+
flags: 1,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fn make_frame_codec_data_type() -> rb_data_type_t {
|
|
125
|
+
rb_data_type_t {
|
|
126
|
+
wrap_struct_name: c"lz4rip_frame_codec".as_ptr(),
|
|
127
|
+
function: rb_data_type_struct__bindgen_ty_1 {
|
|
128
|
+
dmark: None,
|
|
129
|
+
dfree: Some(frame_codec_free),
|
|
130
|
+
dsize: Some(frame_codec_native_size),
|
|
131
|
+
dcompact: None,
|
|
132
|
+
reserved: [std::ptr::null_mut(); 1],
|
|
133
|
+
},
|
|
134
|
+
parent: std::ptr::null(),
|
|
135
|
+
data: std::ptr::null_mut(),
|
|
136
|
+
flags: rb::frozen_shareable_flags(),
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fn make_dict_trainer_data_type() -> rb_data_type_t {
|
|
141
|
+
rb_data_type_t {
|
|
142
|
+
wrap_struct_name: c"lz4rip_dict_trainer".as_ptr(),
|
|
143
|
+
function: rb_data_type_struct__bindgen_ty_1 {
|
|
144
|
+
dmark: None,
|
|
145
|
+
dfree: Some(dict_trainer_free),
|
|
146
|
+
dsize: Some(dict_trainer_native_size),
|
|
147
|
+
dcompact: None,
|
|
148
|
+
reserved: [std::ptr::null_mut(); 1],
|
|
149
|
+
},
|
|
150
|
+
parent: std::ptr::null(),
|
|
151
|
+
data: std::ptr::null_mut(),
|
|
152
|
+
flags: 1,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
unsafe extern "C" fn block_codec_free(ptr: *mut c_void) {
|
|
157
|
+
if ptr.is_null() {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
|
|
162
|
+
drop(Box::from_raw(ptr as *mut BlockCodec));
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
unsafe extern "C" fn frame_codec_free(ptr: *mut c_void) {
|
|
167
|
+
if ptr.is_null() {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
|
|
172
|
+
drop(Box::from_raw(ptr as *mut FrameCodec));
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
unsafe extern "C" fn dict_trainer_free(ptr: *mut c_void) {
|
|
177
|
+
if ptr.is_null() {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
|
|
182
|
+
drop(Box::from_raw(ptr as *mut RbDictTrainer));
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
unsafe extern "C" fn block_codec_native_size(_ptr: *const c_void) -> size_t {
|
|
187
|
+
std::mem::size_of::<BlockCodec>() as size_t
|
|
188
|
+
}
|
|
15
189
|
|
|
16
|
-
|
|
190
|
+
unsafe extern "C" fn frame_codec_native_size(_ptr: *const c_void) -> size_t {
|
|
191
|
+
std::mem::size_of::<FrameCodec>() as size_t
|
|
192
|
+
}
|
|
17
193
|
|
|
18
|
-
fn
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
194
|
+
unsafe extern "C" fn dict_trainer_native_size(_ptr: *const c_void) -> size_t {
|
|
195
|
+
std::mem::size_of::<RbDictTrainer>() as size_t
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
unsafe fn block_codec_ref(value: VALUE) -> RbResult<&'static BlockCodec> {
|
|
199
|
+
unsafe { rb::typed_data_ref(value, block_codec_data_type(), "Lz4rip::BlockCodec") }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
unsafe fn frame_codec_ref(value: VALUE) -> RbResult<&'static FrameCodec> {
|
|
203
|
+
unsafe { rb::typed_data_ref(value, frame_codec_data_type(), "Lz4rip::FrameCodec") }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
unsafe fn dict_trainer_ref(value: VALUE) -> RbResult<&'static RbDictTrainer> {
|
|
207
|
+
unsafe { rb::typed_data_ref(value, dict_trainer_data_type(), "Lz4rip::DictTrainer") }
|
|
24
208
|
}
|
|
25
209
|
|
|
26
210
|
// ---------- module functions ----------
|
|
27
211
|
|
|
28
|
-
fn
|
|
29
|
-
block::get_maximum_output_size(size)
|
|
212
|
+
fn lz4rip_compress_bound_impl(size: VALUE) -> RbResult<VALUE> {
|
|
213
|
+
rb::usize_value(block::get_maximum_output_size(rb::value_to_usize(size)?))
|
|
30
214
|
}
|
|
31
215
|
|
|
32
|
-
fn
|
|
33
|
-
COMPRESSOR_HEAP_SIZE
|
|
216
|
+
fn lz4rip_block_stream_size_impl() -> RbResult<VALUE> {
|
|
217
|
+
rb::usize_value(COMPRESSOR_HEAP_SIZE)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
unsafe extern "C" fn lz4rip_compress_bound(_module: VALUE, size: VALUE) -> VALUE {
|
|
221
|
+
rb::wrap(|| lz4rip_compress_bound_impl(size))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
unsafe extern "C" fn lz4rip_block_stream_size(_module: VALUE) -> VALUE {
|
|
225
|
+
rb::wrap(lz4rip_block_stream_size_impl)
|
|
34
226
|
}
|
|
35
227
|
|
|
36
228
|
// ---------- BlockCodec ----------
|
|
37
229
|
|
|
38
|
-
#[magnus::wrap(class = "Lz4rip::BlockCodec", free_immediately, size)]
|
|
39
230
|
struct BlockCodec {
|
|
40
|
-
compressor: Option<
|
|
41
|
-
decompressor: Option<Decompressor
|
|
231
|
+
compressor: Option<Mutex<DictCompressor>>,
|
|
232
|
+
decompressor: Option<Mutex<Decompressor>>,
|
|
42
233
|
dict_len: usize,
|
|
43
234
|
}
|
|
44
235
|
|
|
45
|
-
fn
|
|
46
|
-
match rb_dict {
|
|
47
|
-
None =>
|
|
236
|
+
fn block_codec_new_impl(class: VALUE, rb_dict: VALUE) -> RbResult<VALUE> {
|
|
237
|
+
let codec = match rb::value_to_option_bytes(rb_dict)? {
|
|
238
|
+
None => BlockCodec {
|
|
48
239
|
compressor: None,
|
|
49
240
|
decompressor: None,
|
|
50
241
|
dict_len: 0,
|
|
51
|
-
}
|
|
52
|
-
Some(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
242
|
+
},
|
|
243
|
+
Some(bytes) => BlockCodec {
|
|
244
|
+
compressor: Some(Mutex::new(DictCompressor::new(&bytes))),
|
|
245
|
+
decompressor: Some(Mutex::new(Decompressor::with_dict(&bytes))),
|
|
246
|
+
dict_len: bytes.len(),
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
unsafe { rb::wrap_typed_data(class, Box::new(codec), block_codec_data_type()) }
|
|
61
251
|
}
|
|
62
252
|
|
|
63
|
-
fn
|
|
253
|
+
fn block_codec_size_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
254
|
+
let rb_self = unsafe { block_codec_ref(rb_self)? };
|
|
64
255
|
if rb_self.compressor.is_some() {
|
|
65
|
-
COMPRESSOR_HEAP_SIZE + rb_self.dict_len
|
|
256
|
+
rb::usize_value(COMPRESSOR_HEAP_SIZE + rb_self.dict_len)
|
|
66
257
|
} else {
|
|
67
|
-
0
|
|
258
|
+
rb::usize_value(0)
|
|
68
259
|
}
|
|
69
260
|
}
|
|
70
261
|
|
|
71
|
-
fn
|
|
72
|
-
rb_self
|
|
262
|
+
fn block_codec_has_dict_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
263
|
+
let rb_self = unsafe { block_codec_ref(rb_self)? };
|
|
264
|
+
Ok(rb::bool_value(rb_self.compressor.is_some()))
|
|
73
265
|
}
|
|
74
266
|
|
|
75
|
-
fn
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
let input: &[u8] = unsafe { rb_input.as_slice() };
|
|
267
|
+
fn block_codec_compress_impl(rb_self: VALUE, rb_input: VALUE) -> RbResult<VALUE> {
|
|
268
|
+
let rb_self = unsafe { block_codec_ref(rb_self)? };
|
|
269
|
+
let mut input = rb::input_bytes(rb_input)?;
|
|
270
|
+
let release_gvl = should_release_compress_gvl(input.len());
|
|
271
|
+
input.lock_for_without_gvl(release_gvl)?;
|
|
81
272
|
|
|
82
273
|
let out = match &rb_self.compressor {
|
|
83
|
-
None => block::compress(input)
|
|
84
|
-
Some(comp) =>
|
|
274
|
+
None => rb::maybe_without_gvl(release_gvl, || block::compress(input.as_slice()))?,
|
|
275
|
+
Some(comp) => with_mutex(
|
|
276
|
+
comp,
|
|
277
|
+
release_gvl,
|
|
278
|
+
"BlockCodec compressor",
|
|
279
|
+
&mut input,
|
|
280
|
+
|comp, input| Ok(comp.compress(input)),
|
|
281
|
+
)?,
|
|
85
282
|
};
|
|
86
283
|
|
|
87
|
-
|
|
284
|
+
rb::new_binary_string(&out)
|
|
88
285
|
}
|
|
89
286
|
|
|
90
|
-
fn
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
let
|
|
287
|
+
fn block_codec_decompress_impl(
|
|
288
|
+
rb_self: VALUE,
|
|
289
|
+
rb_input: VALUE,
|
|
290
|
+
decompressed_size: VALUE,
|
|
291
|
+
) -> RbResult<VALUE> {
|
|
292
|
+
let rb_self = unsafe { block_codec_ref(rb_self)? };
|
|
293
|
+
let decompressed_size = rb::value_to_usize(decompressed_size)?;
|
|
294
|
+
let mut compressed = rb::input_bytes(rb_input)?;
|
|
97
295
|
|
|
98
296
|
let result = match &rb_self.decompressor {
|
|
99
|
-
None => block::decompress(compressed, decompressed_size),
|
|
100
|
-
Some(decomp) =>
|
|
297
|
+
None => block::decompress(compressed.as_slice(), decompressed_size),
|
|
298
|
+
Some(decomp) => with_mutex(
|
|
299
|
+
decomp,
|
|
300
|
+
false,
|
|
301
|
+
"BlockCodec decompressor",
|
|
302
|
+
&mut compressed,
|
|
303
|
+
|decomp, compressed| Ok(decomp.decompress(compressed, decompressed_size)),
|
|
304
|
+
)?,
|
|
101
305
|
};
|
|
102
306
|
|
|
103
307
|
match result {
|
|
104
|
-
Ok(data) =>
|
|
105
|
-
Err(e) => Err(
|
|
106
|
-
decompress_error(
|
|
308
|
+
Ok(data) => rb::new_binary_string(&data),
|
|
309
|
+
Err(e) => Err(RubyErr::new(
|
|
310
|
+
decompress_error(),
|
|
107
311
|
format!("lz4 block decode failed: {e}"),
|
|
108
312
|
)),
|
|
109
313
|
}
|
|
110
314
|
}
|
|
111
315
|
|
|
316
|
+
unsafe extern "C" fn block_codec_new(class: VALUE, rb_dict: VALUE) -> VALUE {
|
|
317
|
+
rb::wrap(|| block_codec_new_impl(class, rb_dict))
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
unsafe extern "C" fn block_codec_size(rb_self: VALUE) -> VALUE {
|
|
321
|
+
rb::wrap(|| block_codec_size_impl(rb_self))
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
unsafe extern "C" fn block_codec_has_dict(rb_self: VALUE) -> VALUE {
|
|
325
|
+
rb::wrap(|| block_codec_has_dict_impl(rb_self))
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
unsafe extern "C" fn block_codec_compress(rb_self: VALUE, rb_input: VALUE) -> VALUE {
|
|
329
|
+
rb::wrap(|| block_codec_compress_impl(rb_self, rb_input))
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
unsafe extern "C" fn block_codec_decompress(
|
|
333
|
+
rb_self: VALUE,
|
|
334
|
+
rb_input: VALUE,
|
|
335
|
+
decompressed_size: VALUE,
|
|
336
|
+
) -> VALUE {
|
|
337
|
+
rb::wrap(|| block_codec_decompress_impl(rb_self, rb_input, decompressed_size))
|
|
338
|
+
}
|
|
339
|
+
|
|
112
340
|
// ---------- FrameCodec ----------
|
|
113
341
|
|
|
114
|
-
#[magnus::wrap(class = "Lz4rip::FrameCodec", free_immediately, size)]
|
|
115
342
|
struct FrameCodec {
|
|
116
343
|
dict: Option<DictBound>,
|
|
117
344
|
}
|
|
@@ -121,26 +348,33 @@ struct DictBound {
|
|
|
121
348
|
id: u32,
|
|
122
349
|
}
|
|
123
350
|
|
|
124
|
-
fn
|
|
125
|
-
|
|
126
|
-
rb_dict
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
351
|
+
fn frame_codec_new_impl(class: VALUE, rb_dict: VALUE, id: VALUE) -> RbResult<VALUE> {
|
|
352
|
+
let id = rb::value_to_u32(id)?;
|
|
353
|
+
let dict = rb::value_to_option_bytes(rb_dict)?.map(|bytes| DictBound { bytes, id });
|
|
354
|
+
let codec = unsafe {
|
|
355
|
+
rb::wrap_typed_data(
|
|
356
|
+
class,
|
|
357
|
+
Box::new(FrameCodec { dict }),
|
|
358
|
+
frame_codec_data_type(),
|
|
359
|
+
)?
|
|
360
|
+
};
|
|
361
|
+
rb::make_shareable(codec)?;
|
|
362
|
+
Ok(codec)
|
|
135
363
|
}
|
|
136
364
|
|
|
137
|
-
fn
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
)
|
|
142
|
-
|
|
365
|
+
fn frame_codec_compress_impl(rb_self: VALUE, rb_input: VALUE) -> RbResult<VALUE> {
|
|
366
|
+
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
367
|
+
let mut input = rb::input_bytes(rb_input)?;
|
|
368
|
+
let release_gvl = should_release_compress_gvl(input.len());
|
|
369
|
+
input.lock_for_without_gvl(release_gvl)?;
|
|
370
|
+
|
|
371
|
+
let out = rb::maybe_without_gvl(release_gvl, || compress_frame(rb_self, input.as_slice()))?
|
|
372
|
+
.map_err(RubyErr::runtime)?;
|
|
143
373
|
|
|
374
|
+
rb::new_binary_string(&out)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
fn compress_frame(rb_self: &FrameCodec, input: &[u8]) -> Result<Vec<u8>, String> {
|
|
144
378
|
let buf = Vec::new();
|
|
145
379
|
let mut enc = match &rb_self.dict {
|
|
146
380
|
None => {
|
|
@@ -149,194 +383,294 @@ fn frame_codec_compress(
|
|
|
149
383
|
}
|
|
150
384
|
Some(d) => {
|
|
151
385
|
let info = FrameInfo::new().block_mode(BlockMode::Linked);
|
|
152
|
-
FrameEncoder::with_dictionary(buf, &d.bytes, d.id, Some(info))
|
|
153
|
-
|
|
154
|
-
ruby.exception_runtime_error(),
|
|
155
|
-
format!("lz4 frame compress failed: {e}"),
|
|
156
|
-
)
|
|
157
|
-
})?
|
|
386
|
+
FrameEncoder::with_dictionary(buf, &d.bytes, d.id, Some(info))
|
|
387
|
+
.map_err(|e| format!("lz4 frame compress failed: {e}"))?
|
|
158
388
|
}
|
|
159
389
|
};
|
|
160
390
|
|
|
161
|
-
enc.write_all(input)
|
|
162
|
-
|
|
163
|
-
ruby.exception_runtime_error(),
|
|
164
|
-
format!("lz4 frame compress failed: {e}"),
|
|
165
|
-
)
|
|
166
|
-
})?;
|
|
167
|
-
|
|
168
|
-
let out = enc.finish().map_err(|e| {
|
|
169
|
-
Error::new(
|
|
170
|
-
ruby.exception_runtime_error(),
|
|
171
|
-
format!("lz4 frame compress failed: {e}"),
|
|
172
|
-
)
|
|
173
|
-
})?;
|
|
391
|
+
enc.write_all(input)
|
|
392
|
+
.map_err(|e| format!("lz4 frame compress failed: {e}"))?;
|
|
174
393
|
|
|
175
|
-
|
|
394
|
+
enc.finish()
|
|
395
|
+
.map_err(|e| format!("lz4 frame compress failed: {e}"))
|
|
176
396
|
}
|
|
177
397
|
|
|
178
|
-
fn
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
) ->
|
|
183
|
-
let
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
398
|
+
fn frame_codec_decompress_impl(
|
|
399
|
+
rb_self: VALUE,
|
|
400
|
+
rb_input: VALUE,
|
|
401
|
+
max_decompressed_size: VALUE,
|
|
402
|
+
) -> RbResult<VALUE> {
|
|
403
|
+
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
404
|
+
let max_decompressed_size = rb::value_to_option_usize(max_decompressed_size)?;
|
|
405
|
+
let mut input = rb::input_bytes(rb_input)?;
|
|
406
|
+
let release_gvl = should_release_frame_decompress_gvl(input.len());
|
|
407
|
+
input.lock_for_without_gvl(release_gvl)?;
|
|
408
|
+
|
|
409
|
+
if input.len() < 4 || input.as_slice()[..4] != LZ4_FRAME_MAGIC {
|
|
410
|
+
return Err(RubyErr::new(
|
|
411
|
+
decompress_error(),
|
|
188
412
|
"lz4 frame decode failed: bad magic (input is not an LZ4 frame)",
|
|
189
413
|
));
|
|
190
414
|
}
|
|
191
415
|
|
|
192
|
-
let
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
416
|
+
let out = rb::maybe_without_gvl(release_gvl, || {
|
|
417
|
+
decompress_frame(rb_self, input.as_slice(), max_decompressed_size)
|
|
418
|
+
})?
|
|
419
|
+
.map_err(|e| RubyErr::new(decompress_error(), e))?;
|
|
420
|
+
|
|
421
|
+
rb::new_binary_string(&out)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
fn decompress_frame(
|
|
425
|
+
rb_self: &FrameCodec,
|
|
426
|
+
input: &[u8],
|
|
427
|
+
max_decompressed_size: Option<usize>,
|
|
428
|
+
) -> Result<Vec<u8>, String> {
|
|
429
|
+
let mut dec = FrameDecoder::with_options(
|
|
430
|
+
Cursor::new(input),
|
|
431
|
+
FrameDecoderOptions {
|
|
432
|
+
dictionary: rb_self.dict.as_ref().map(|d| (d.bytes.as_slice(), d.id)),
|
|
433
|
+
max_output: max_decompressed_size,
|
|
434
|
+
},
|
|
435
|
+
);
|
|
196
436
|
|
|
197
437
|
let mut out = Vec::new();
|
|
198
|
-
dec.read_to_end(&mut out)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
438
|
+
dec.read_to_end(&mut out)
|
|
439
|
+
.map_err(|e| format!("lz4 frame decode failed: {e}"))?;
|
|
440
|
+
Ok(out)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
fn frame_codec_size_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
444
|
+
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
445
|
+
rb::usize_value(rb_self.dict.as_ref().map_or(0, |d| d.bytes.len()))
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
fn frame_codec_has_dict_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
449
|
+
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
450
|
+
Ok(rb::bool_value(rb_self.dict.is_some()))
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
fn frame_codec_id_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
454
|
+
let rb_self = unsafe { frame_codec_ref(rb_self)? };
|
|
455
|
+
rb::u32_option_value(rb_self.dict.as_ref().map(|d| d.id))
|
|
456
|
+
}
|
|
204
457
|
|
|
205
|
-
|
|
458
|
+
unsafe extern "C" fn frame_codec_new(class: VALUE, rb_dict: VALUE, id: VALUE) -> VALUE {
|
|
459
|
+
rb::wrap(|| frame_codec_new_impl(class, rb_dict, id))
|
|
206
460
|
}
|
|
207
461
|
|
|
208
|
-
fn
|
|
209
|
-
|
|
462
|
+
unsafe extern "C" fn frame_codec_compress(rb_self: VALUE, rb_input: VALUE) -> VALUE {
|
|
463
|
+
rb::wrap(|| frame_codec_compress_impl(rb_self, rb_input))
|
|
210
464
|
}
|
|
211
465
|
|
|
212
|
-
|
|
213
|
-
rb_self
|
|
466
|
+
unsafe extern "C" fn frame_codec_decompress(
|
|
467
|
+
rb_self: VALUE,
|
|
468
|
+
rb_input: VALUE,
|
|
469
|
+
max_decompressed_size: VALUE,
|
|
470
|
+
) -> VALUE {
|
|
471
|
+
rb::wrap(|| frame_codec_decompress_impl(rb_self, rb_input, max_decompressed_size))
|
|
214
472
|
}
|
|
215
473
|
|
|
216
|
-
fn
|
|
217
|
-
|
|
474
|
+
unsafe extern "C" fn frame_codec_size(rb_self: VALUE) -> VALUE {
|
|
475
|
+
rb::wrap(|| frame_codec_size_impl(rb_self))
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
unsafe extern "C" fn frame_codec_has_dict(rb_self: VALUE) -> VALUE {
|
|
479
|
+
rb::wrap(|| frame_codec_has_dict_impl(rb_self))
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
unsafe extern "C" fn frame_codec_id(rb_self: VALUE) -> VALUE {
|
|
483
|
+
rb::wrap(|| frame_codec_id_impl(rb_self))
|
|
218
484
|
}
|
|
219
485
|
|
|
220
486
|
// ---------- DictTrainer ----------
|
|
221
487
|
|
|
222
488
|
const LZ4_MAX_DISTANCE: usize = 65535;
|
|
223
489
|
|
|
224
|
-
#[magnus::wrap(class = "Lz4rip::DictTrainer", free_immediately, size)]
|
|
225
490
|
struct RbDictTrainer {
|
|
226
|
-
inner:
|
|
491
|
+
inner: Mutex<Option<DictTrainer>>,
|
|
227
492
|
max_dict_size: usize,
|
|
228
493
|
}
|
|
229
494
|
|
|
230
|
-
fn
|
|
495
|
+
fn dict_trainer_new_impl(class: VALUE, max_dict_size: VALUE) -> RbResult<VALUE> {
|
|
496
|
+
let max_dict_size = rb::value_to_usize(max_dict_size)?;
|
|
231
497
|
let capped = max_dict_size.min(LZ4_MAX_DISTANCE);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
498
|
+
unsafe {
|
|
499
|
+
rb::wrap_typed_data(
|
|
500
|
+
class,
|
|
501
|
+
Box::new(RbDictTrainer {
|
|
502
|
+
max_dict_size: capped,
|
|
503
|
+
inner: Mutex::new(Some(DictTrainer::new(max_dict_size))),
|
|
504
|
+
}),
|
|
505
|
+
dict_trainer_data_type(),
|
|
506
|
+
)
|
|
235
507
|
}
|
|
236
508
|
}
|
|
237
509
|
|
|
238
|
-
fn
|
|
239
|
-
|
|
240
|
-
rb_self
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
)
|
|
249
|
-
})?;
|
|
250
|
-
let data: &[u8] = unsafe { rb_data.as_slice() };
|
|
510
|
+
fn dict_trainer_add_sample_impl(rb_self: VALUE, rb_data: VALUE) -> RbResult<VALUE> {
|
|
511
|
+
let data = rb::input_bytes(rb_data)?;
|
|
512
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
513
|
+
let mut borrow = rb_self
|
|
514
|
+
.inner
|
|
515
|
+
.lock()
|
|
516
|
+
.map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
|
|
517
|
+
let trainer = borrow
|
|
518
|
+
.as_mut()
|
|
519
|
+
.ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
|
|
251
520
|
let sample = if data.len() > rb_self.max_dict_size {
|
|
252
|
-
&data[..rb_self.max_dict_size]
|
|
521
|
+
&data.as_slice()[..rb_self.max_dict_size]
|
|
253
522
|
} else {
|
|
254
|
-
data
|
|
523
|
+
data.as_slice()
|
|
255
524
|
};
|
|
256
525
|
trainer.add_sample(sample);
|
|
257
|
-
Ok(())
|
|
526
|
+
Ok(rb::qnil())
|
|
258
527
|
}
|
|
259
528
|
|
|
260
|
-
fn
|
|
261
|
-
let
|
|
262
|
-
borrow
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
529
|
+
fn dict_trainer_sample_count_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
530
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
531
|
+
let borrow = rb_self
|
|
532
|
+
.inner
|
|
533
|
+
.lock()
|
|
534
|
+
.map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
|
|
535
|
+
let value = borrow
|
|
536
|
+
.as_ref()
|
|
537
|
+
.map(|t| t.sample_count())
|
|
538
|
+
.ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
|
|
539
|
+
rb::usize_value(value)
|
|
268
540
|
}
|
|
269
541
|
|
|
270
|
-
fn
|
|
271
|
-
let
|
|
272
|
-
borrow
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
542
|
+
fn dict_trainer_total_bytes_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
543
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
544
|
+
let borrow = rb_self
|
|
545
|
+
.inner
|
|
546
|
+
.lock()
|
|
547
|
+
.map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
|
|
548
|
+
let value = borrow
|
|
549
|
+
.as_ref()
|
|
550
|
+
.map(|t| t.total_bytes())
|
|
551
|
+
.ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
|
|
552
|
+
rb::usize_value(value)
|
|
278
553
|
}
|
|
279
554
|
|
|
280
|
-
fn
|
|
281
|
-
let
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
)
|
|
286
|
-
|
|
555
|
+
fn dict_trainer_train_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
556
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
557
|
+
let trainer = rb_self
|
|
558
|
+
.inner
|
|
559
|
+
.lock()
|
|
560
|
+
.map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?
|
|
561
|
+
.take()
|
|
562
|
+
.ok_or_else(|| RubyErr::runtime("DictTrainer already consumed by #train"))?;
|
|
287
563
|
let dict = trainer.train();
|
|
288
|
-
|
|
564
|
+
rb::new_binary_string(&dict)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
fn dict_trainer_max_dict_size_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
568
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
569
|
+
rb::usize_value(rb_self.max_dict_size)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
fn dict_trainer_trained_impl(rb_self: VALUE) -> RbResult<VALUE> {
|
|
573
|
+
let rb_self = unsafe { dict_trainer_ref(rb_self)? };
|
|
574
|
+
let borrow = rb_self
|
|
575
|
+
.inner
|
|
576
|
+
.lock()
|
|
577
|
+
.map_err(|_| RubyErr::runtime("DictTrainer mutex poisoned"))?;
|
|
578
|
+
Ok(rb::bool_value(borrow.is_none()))
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
unsafe extern "C" fn dict_trainer_new(class: VALUE, max_dict_size: VALUE) -> VALUE {
|
|
582
|
+
rb::wrap(|| dict_trainer_new_impl(class, max_dict_size))
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
unsafe extern "C" fn dict_trainer_add_sample(rb_self: VALUE, rb_data: VALUE) -> VALUE {
|
|
586
|
+
rb::wrap(|| dict_trainer_add_sample_impl(rb_self, rb_data))
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
unsafe extern "C" fn dict_trainer_sample_count(rb_self: VALUE) -> VALUE {
|
|
590
|
+
rb::wrap(|| dict_trainer_sample_count_impl(rb_self))
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
unsafe extern "C" fn dict_trainer_total_bytes(rb_self: VALUE) -> VALUE {
|
|
594
|
+
rb::wrap(|| dict_trainer_total_bytes_impl(rb_self))
|
|
289
595
|
}
|
|
290
596
|
|
|
291
|
-
fn
|
|
292
|
-
rb_self
|
|
597
|
+
unsafe extern "C" fn dict_trainer_train(rb_self: VALUE) -> VALUE {
|
|
598
|
+
rb::wrap(|| dict_trainer_train_impl(rb_self))
|
|
293
599
|
}
|
|
294
600
|
|
|
295
|
-
fn
|
|
296
|
-
|
|
601
|
+
unsafe extern "C" fn dict_trainer_max_dict_size(rb_self: VALUE) -> VALUE {
|
|
602
|
+
rb::wrap(|| dict_trainer_max_dict_size_impl(rb_self))
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
unsafe extern "C" fn dict_trainer_trained(rb_self: VALUE) -> VALUE {
|
|
606
|
+
rb::wrap(|| dict_trainer_trained_impl(rb_self))
|
|
297
607
|
}
|
|
298
608
|
|
|
299
609
|
// ---------- module init ----------
|
|
300
610
|
|
|
301
|
-
#
|
|
302
|
-
|
|
303
|
-
|
|
611
|
+
/// # Safety
|
|
612
|
+
///
|
|
613
|
+
/// Ruby calls this function while loading the native extension. The Ruby VM
|
|
614
|
+
/// must be initialized, and the symbol must only be entered by Ruby's extension
|
|
615
|
+
/// loader.
|
|
616
|
+
#[no_mangle]
|
|
617
|
+
pub unsafe extern "C" fn Init_lz4rip() {
|
|
618
|
+
rb::wrap_init(init);
|
|
619
|
+
}
|
|
304
620
|
|
|
305
|
-
|
|
621
|
+
fn init() -> RbResult<()> {
|
|
622
|
+
#[cfg(ruby_engine = "mri")]
|
|
623
|
+
unsafe {
|
|
624
|
+
rb_sys::rb_ext_ractor_safe(true);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
let module = unsafe { rb::define_module(c"Lz4rip")? };
|
|
306
628
|
|
|
307
629
|
let decompress_error_class =
|
|
308
|
-
module
|
|
630
|
+
unsafe { rb::define_error_under(module, c"DecompressError", rb_sys::rb_eStandardError)? };
|
|
309
631
|
DECOMPRESS_ERROR
|
|
310
|
-
.set(
|
|
632
|
+
.set(GlobalValue(decompress_error_class))
|
|
311
633
|
.unwrap_or_else(|_| panic!("init called more than once"));
|
|
312
634
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
codec_class
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
trainer_class
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
635
|
+
unsafe {
|
|
636
|
+
rb::define_module_function_1(module, c"compress_bound", lz4rip_compress_bound)?;
|
|
637
|
+
rb::define_module_function_0(module, c"block_stream_size", lz4rip_block_stream_size)?;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
let codec_class = unsafe { rb::define_class_under(module, c"BlockCodec", rb_sys::rb_cObject)? };
|
|
641
|
+
unsafe {
|
|
642
|
+
rb::undef_alloc_func(codec_class)?;
|
|
643
|
+
rb::define_singleton_method_1(codec_class, c"_native_new", block_codec_new)?;
|
|
644
|
+
rb::define_method_0(codec_class, c"size", block_codec_size)?;
|
|
645
|
+
rb::define_method_0(codec_class, c"has_dict?", block_codec_has_dict)?;
|
|
646
|
+
rb::define_method_1(codec_class, c"compress", block_codec_compress)?;
|
|
647
|
+
rb::define_method_2(codec_class, c"_decompress", block_codec_decompress)?;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
let trainer_class =
|
|
651
|
+
unsafe { rb::define_class_under(module, c"DictTrainer", rb_sys::rb_cObject)? };
|
|
652
|
+
unsafe {
|
|
653
|
+
rb::undef_alloc_func(trainer_class)?;
|
|
654
|
+
rb::define_singleton_method_1(trainer_class, c"_native_new", dict_trainer_new)?;
|
|
655
|
+
rb::define_method_1(trainer_class, c"add_sample", dict_trainer_add_sample)?;
|
|
656
|
+
rb::define_method_0(trainer_class, c"sample_count", dict_trainer_sample_count)?;
|
|
657
|
+
rb::define_method_0(trainer_class, c"total_bytes", dict_trainer_total_bytes)?;
|
|
658
|
+
rb::define_method_0(trainer_class, c"train", dict_trainer_train)?;
|
|
659
|
+
rb::define_method_0(trainer_class, c"max_dict_size", dict_trainer_max_dict_size)?;
|
|
660
|
+
rb::define_method_0(trainer_class, c"trained?", dict_trainer_trained)?;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
let frame_codec_class =
|
|
664
|
+
unsafe { rb::define_class_under(module, c"FrameCodec", rb_sys::rb_cObject)? };
|
|
665
|
+
unsafe {
|
|
666
|
+
rb::undef_alloc_func(frame_codec_class)?;
|
|
667
|
+
rb::define_singleton_method_2(frame_codec_class, c"_native_new", frame_codec_new)?;
|
|
668
|
+
rb::define_method_1(frame_codec_class, c"compress", frame_codec_compress)?;
|
|
669
|
+
rb::define_method_2(frame_codec_class, c"_decompress", frame_codec_decompress)?;
|
|
670
|
+
rb::define_method_0(frame_codec_class, c"size", frame_codec_size)?;
|
|
671
|
+
rb::define_method_0(frame_codec_class, c"has_dict?", frame_codec_has_dict)?;
|
|
672
|
+
rb::define_method_0(frame_codec_class, c"id", frame_codec_id)?;
|
|
673
|
+
}
|
|
340
674
|
|
|
341
675
|
Ok(())
|
|
342
676
|
}
|