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