lz4rip 0.1.1 → 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 +16 -0
- data/Cargo.lock +38 -74
- data/README.md +12 -34
- data/ext/lz4rip/Cargo.toml +3 -3
- data/ext/lz4rip/build.rs +23 -0
- data/ext/lz4rip/src/lib.rs +535 -205
- 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 +7 -4
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};
|
|
7
|
+
|
|
8
|
+
use lz4::block::{self, Decompressor, DictCompressor, DictTrainer};
|
|
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};
|
|
8
11
|
|
|
9
|
-
use
|
|
10
|
-
use lz4::frame::{BlockMode, FrameDecoder, FrameEncoder, FrameInfo};
|
|
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
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------- typed data ----------
|
|
69
|
+
|
|
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
|
+
}
|
|
15
186
|
|
|
16
|
-
|
|
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
|
+
}
|
|
17
190
|
|
|
18
|
-
fn
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
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,214 +329,336 @@ 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
|
-
|
|
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)?;
|
|
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
|
+
}
|
|
143
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 => {
|
|
147
369
|
let info = FrameInfo::new().block_mode(BlockMode::Linked);
|
|
148
370
|
FrameEncoder::with_frame_info(info, buf)
|
|
149
371
|
}
|
|
150
|
-
Some(d) =>
|
|
372
|
+
Some(d) => {
|
|
373
|
+
let info = FrameInfo::new().block_mode(BlockMode::Linked);
|
|
374
|
+
FrameEncoder::with_dictionary(buf, &d.bytes, d.id, Some(info))
|
|
375
|
+
.map_err(|e| format!("lz4 frame compress failed: {e}"))?
|
|
376
|
+
}
|
|
151
377
|
};
|
|
152
378
|
|
|
153
|
-
enc.write_all(input)
|
|
154
|
-
|
|
155
|
-
ruby.exception_runtime_error(),
|
|
156
|
-
format!("lz4 frame compress failed: {e}"),
|
|
157
|
-
)
|
|
158
|
-
})?;
|
|
159
|
-
|
|
160
|
-
let out = enc.finish().map_err(|e| {
|
|
161
|
-
Error::new(
|
|
162
|
-
ruby.exception_runtime_error(),
|
|
163
|
-
format!("lz4 frame compress failed: {e}"),
|
|
164
|
-
)
|
|
165
|
-
})?;
|
|
379
|
+
enc.write_all(input)
|
|
380
|
+
.map_err(|e| format!("lz4 frame compress failed: {e}"))?;
|
|
166
381
|
|
|
167
|
-
|
|
382
|
+
enc.finish()
|
|
383
|
+
.map_err(|e| format!("lz4 frame compress failed: {e}"))
|
|
168
384
|
}
|
|
169
385
|
|
|
170
|
-
fn
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
) ->
|
|
175
|
-
let
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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(),
|
|
180
400
|
"lz4 frame decode failed: bad magic (input is not an LZ4 frame)",
|
|
181
401
|
));
|
|
182
402
|
}
|
|
183
403
|
|
|
184
|
-
let
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
+
);
|
|
188
424
|
|
|
189
425
|
let mut out = Vec::new();
|
|
190
|
-
dec.read_to_end(&mut out)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
)
|
|
195
|
-
})?;
|
|
426
|
+
dec.read_to_end(&mut out)
|
|
427
|
+
.map_err(|e| format!("lz4 frame decode failed: {e}"))?;
|
|
428
|
+
Ok(out)
|
|
429
|
+
}
|
|
196
430
|
|
|
197
|
-
|
|
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()))
|
|
198
434
|
}
|
|
199
435
|
|
|
200
|
-
fn
|
|
201
|
-
rb_self
|
|
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()))
|
|
202
439
|
}
|
|
203
440
|
|
|
204
|
-
fn
|
|
205
|
-
rb_self
|
|
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))
|
|
206
444
|
}
|
|
207
445
|
|
|
208
|
-
fn
|
|
209
|
-
|
|
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
|
+
}
|
|
453
|
+
|
|
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))
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
unsafe extern "C" fn frame_codec_size(rb_self: VALUE) -> VALUE {
|
|
463
|
+
rb::wrap(|| frame_codec_size_impl(rb_self))
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
unsafe extern "C" fn frame_codec_has_dict(rb_self: VALUE) -> VALUE {
|
|
467
|
+
rb::wrap(|| frame_codec_has_dict_impl(rb_self))
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
unsafe extern "C" fn frame_codec_id(rb_self: VALUE) -> VALUE {
|
|
471
|
+
rb::wrap(|| frame_codec_id_impl(rb_self))
|
|
210
472
|
}
|
|
211
473
|
|
|
212
474
|
// ---------- DictTrainer ----------
|
|
213
475
|
|
|
214
476
|
const LZ4_MAX_DISTANCE: usize = 65535;
|
|
215
477
|
|
|
216
|
-
#[magnus::wrap(class = "Lz4rip::DictTrainer", free_immediately, size)]
|
|
217
478
|
struct RbDictTrainer {
|
|
218
|
-
inner:
|
|
479
|
+
inner: Mutex<Option<DictTrainer>>,
|
|
219
480
|
max_dict_size: usize,
|
|
220
481
|
}
|
|
221
482
|
|
|
222
|
-
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)?;
|
|
223
485
|
let capped = max_dict_size.min(LZ4_MAX_DISTANCE);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
+
)
|
|
227
495
|
}
|
|
228
496
|
}
|
|
229
497
|
|
|
230
|
-
fn
|
|
231
|
-
|
|
232
|
-
rb_self
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
let trainer = borrow
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
)
|
|
241
|
-
})?;
|
|
242
|
-
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)?;
|
|
243
508
|
let sample = if data.len() > rb_self.max_dict_size {
|
|
244
|
-
&data[..rb_self.max_dict_size]
|
|
509
|
+
&data.as_slice()[..rb_self.max_dict_size]
|
|
245
510
|
} else {
|
|
246
|
-
data
|
|
511
|
+
data.as_slice()
|
|
247
512
|
};
|
|
248
513
|
trainer.add_sample(sample);
|
|
249
|
-
Ok(())
|
|
514
|
+
Ok(rb::qnil())
|
|
250
515
|
}
|
|
251
516
|
|
|
252
|
-
fn
|
|
253
|
-
let
|
|
254
|
-
borrow
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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)
|
|
260
528
|
}
|
|
261
529
|
|
|
262
|
-
fn
|
|
263
|
-
let
|
|
264
|
-
borrow
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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)
|
|
270
541
|
}
|
|
271
542
|
|
|
272
|
-
fn
|
|
273
|
-
let
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
)
|
|
278
|
-
|
|
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"))?;
|
|
279
551
|
let dict = trainer.train();
|
|
280
|
-
|
|
552
|
+
rb::new_binary_string(&dict)
|
|
281
553
|
}
|
|
282
554
|
|
|
283
|
-
fn
|
|
284
|
-
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)
|
|
285
558
|
}
|
|
286
559
|
|
|
287
|
-
fn
|
|
288
|
-
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))
|
|
289
595
|
}
|
|
290
596
|
|
|
291
597
|
// ---------- module init ----------
|
|
292
598
|
|
|
293
|
-
#
|
|
294
|
-
|
|
295
|
-
|
|
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
|
+
}
|
|
296
608
|
|
|
297
|
-
|
|
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")? };
|
|
298
616
|
|
|
299
617
|
let decompress_error_class =
|
|
300
|
-
module
|
|
618
|
+
unsafe { rb::define_error_under(module, c"DecompressError", rb_sys::rb_eStandardError)? };
|
|
301
619
|
DECOMPRESS_ERROR
|
|
302
|
-
.set(
|
|
620
|
+
.set(GlobalValue(decompress_error_class))
|
|
303
621
|
.unwrap_or_else(|_| panic!("init called more than once"));
|
|
304
622
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
codec_class
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
trainer_class
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
+
}
|
|
332
662
|
|
|
333
663
|
Ok(())
|
|
334
664
|
}
|
|
@@ -350,7 +680,7 @@ mod tests {
|
|
|
350
680
|
let dict = b"common log prefix: ".to_vec();
|
|
351
681
|
let msg = b"common log prefix: event=login user=alice".to_vec();
|
|
352
682
|
|
|
353
|
-
let mut comp =
|
|
683
|
+
let mut comp = DictCompressor::new(&dict);
|
|
354
684
|
let ct_dict = comp.compress(&msg);
|
|
355
685
|
let decomp = Decompressor::with_dict(&dict);
|
|
356
686
|
let pt = decomp.decompress(&ct_dict, msg.len()).unwrap();
|