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.
@@ -1,117 +1,325 @@
1
- use magnus::{
2
- exception::ExceptionClass, function, method, prelude::*, r_string::RString, value::Opaque,
3
- Error, Ruby,
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::sync::OnceLock;
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
- static DECOMPRESS_ERROR: OnceLock<Opaque<ExceptionClass>> = OnceLock::new();
68
+ // ---------- typed data ----------
17
69
 
18
- fn decompress_error(ruby: &Ruby) -> ExceptionClass {
19
- ruby.get_inner(
20
- *DECOMPRESS_ERROR
21
- .get()
22
- .expect("DecompressError not initialized"),
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 lz4rip_compress_bound(_ruby: &Ruby, size: usize) -> usize {
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 lz4rip_block_stream_size(_ruby: &Ruby) -> usize {
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<RefCell<DictCompressor>>,
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 block_codec_new(_ruby: &Ruby, rb_dict: Option<RString>) -> Result<BlockCodec, Error> {
46
- match rb_dict {
47
- None => Ok(BlockCodec {
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(rb_dict) => {
53
- let bytes: Vec<u8> = unsafe { rb_dict.as_slice().to_vec() };
54
- Ok(BlockCodec {
55
- compressor: Some(RefCell::new(DictCompressor::new(&bytes))),
56
- decompressor: Some(Decompressor::with_dict(&bytes)),
57
- dict_len: bytes.len(),
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 block_codec_size(rb_self: &BlockCodec) -> usize {
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 block_codec_has_dict(rb_self: &BlockCodec) -> bool {
72
- rb_self.compressor.is_some()
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 block_codec_compress(
76
- ruby: &Ruby,
77
- rb_self: &BlockCodec,
78
- rb_input: RString,
79
- ) -> Result<RString, Error> {
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.borrow_mut().compress(input),
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
- Ok(ruby.str_from_slice(&out))
269
+ rb::new_binary_string(&out)
88
270
  }
89
271
 
90
- fn block_codec_decompress(
91
- ruby: &Ruby,
92
- rb_self: &BlockCodec,
93
- rb_input: RString,
94
- decompressed_size: usize,
95
- ) -> Result<RString, Error> {
96
- let compressed: &[u8] = unsafe { rb_input.as_slice() };
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.decompress(compressed, decompressed_size),
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) => Ok(ruby.str_from_slice(&data)),
105
- Err(e) => Err(Error::new(
106
- decompress_error(ruby),
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 frame_codec_initialize(
125
- _ruby: &Ruby,
126
- rb_dict: Option<RString>,
127
- id: u32,
128
- ) -> Result<FrameCodec, Error> {
129
- let dict = rb_dict.map(|s| {
130
- let bytes: Vec<u8> = unsafe { s.as_slice().to_vec() };
131
- s.freeze();
132
- DictBound { bytes, id }
133
- });
134
- Ok(FrameCodec { dict })
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 frame_codec_compress(
138
- ruby: &Ruby,
139
- rb_self: &FrameCodec,
140
- rb_input: RString,
141
- ) -> Result<RString, Error> {
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)).map_err(|e| {
153
- Error::new(
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).map_err(|e| {
162
- Error::new(
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
- Ok(ruby.str_from_slice(&out))
382
+ enc.finish()
383
+ .map_err(|e| format!("lz4 frame compress failed: {e}"))
176
384
  }
177
385
 
178
- fn frame_codec_decompress(
179
- ruby: &Ruby,
180
- rb_self: &FrameCodec,
181
- rb_input: RString,
182
- ) -> Result<RString, Error> {
183
- let input: &[u8] = unsafe { rb_input.as_slice() };
184
-
185
- if input.len() < 4 || input[..4] != LZ4_FRAME_MAGIC {
186
- return Err(Error::new(
187
- decompress_error(ruby),
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 mut dec = match &rb_self.dict {
193
- None => FrameDecoder::new(Cursor::new(input)),
194
- Some(d) => FrameDecoder::with_dictionary(Cursor::new(input), &d.bytes, d.id),
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).map_err(|e| {
199
- Error::new(
200
- decompress_error(ruby),
201
- format!("lz4 frame decode failed: {e}"),
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
- Ok(ruby.str_from_slice(&out))
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: &FrameCodec) -> usize {
209
- rb_self.dict.as_ref().map_or(0, |d| d.bytes.len())
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: &FrameCodec) -> bool {
213
- rb_self.dict.is_some()
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: &FrameCodec) -> Option<u32> {
217
- rb_self.dict.as_ref().map(|d| d.id)
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: RefCell<Option<DictTrainer>>,
479
+ inner: Mutex<Option<DictTrainer>>,
227
480
  max_dict_size: usize,
228
481
  }
229
482
 
230
- fn dict_trainer_new(_ruby: &Ruby, max_dict_size: usize) -> RbDictTrainer {
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
- RbDictTrainer {
233
- max_dict_size: capped,
234
- inner: RefCell::new(Some(DictTrainer::new(max_dict_size))),
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 dict_trainer_add_sample(
239
- ruby: &Ruby,
240
- rb_self: &RbDictTrainer,
241
- rb_data: RString,
242
- ) -> Result<(), Error> {
243
- let mut borrow = rb_self.inner.borrow_mut();
244
- let trainer = borrow.as_mut().ok_or_else(|| {
245
- Error::new(
246
- ruby.exception_runtime_error(),
247
- "DictTrainer already consumed by #train",
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 dict_trainer_sample_count(ruby: &Ruby, rb_self: &RbDictTrainer) -> Result<usize, Error> {
261
- let borrow = rb_self.inner.borrow();
262
- borrow.as_ref().map(|t| t.sample_count()).ok_or_else(|| {
263
- Error::new(
264
- ruby.exception_runtime_error(),
265
- "DictTrainer already consumed by #train",
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 dict_trainer_total_bytes(ruby: &Ruby, rb_self: &RbDictTrainer) -> Result<usize, Error> {
271
- let borrow = rb_self.inner.borrow();
272
- borrow.as_ref().map(|t| t.total_bytes()).ok_or_else(|| {
273
- Error::new(
274
- ruby.exception_runtime_error(),
275
- "DictTrainer already consumed by #train",
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 dict_trainer_train(ruby: &Ruby, rb_self: &RbDictTrainer) -> Result<RString, Error> {
281
- let trainer = rb_self.inner.borrow_mut().take().ok_or_else(|| {
282
- Error::new(
283
- ruby.exception_runtime_error(),
284
- "DictTrainer already consumed by #train",
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
- Ok(ruby.str_from_slice(&dict))
552
+ rb::new_binary_string(&dict)
289
553
  }
290
554
 
291
- fn dict_trainer_max_dict_size(rb_self: &RbDictTrainer) -> usize {
292
- rb_self.max_dict_size
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 dict_trainer_trained(rb_self: &RbDictTrainer) -> bool {
296
- rb_self.inner.borrow().is_none()
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
- #[magnus::init]
302
- fn init(ruby: &Ruby) -> Result<(), Error> {
303
- unsafe { rb_sys::rb_ext_ractor_safe(true) };
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
- let module = ruby.define_module("Lz4rip")?;
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.define_error("DecompressError", ruby.exception_standard_error())?;
618
+ unsafe { rb::define_error_under(module, c"DecompressError", rb_sys::rb_eStandardError)? };
309
619
  DECOMPRESS_ERROR
310
- .set(Opaque::from(decompress_error_class))
620
+ .set(GlobalValue(decompress_error_class))
311
621
  .unwrap_or_else(|_| panic!("init called more than once"));
312
622
 
313
- module.define_module_function("compress_bound", function!(lz4rip_compress_bound, 1))?;
314
- module.define_module_function("block_stream_size", function!(lz4rip_block_stream_size, 0))?;
315
-
316
- let codec_class = module.define_class("BlockCodec", ruby.class_object())?;
317
- codec_class.define_singleton_method("_native_new", function!(block_codec_new, 1))?;
318
- codec_class.define_method("size", method!(block_codec_size, 0))?;
319
- codec_class.define_method("has_dict?", method!(block_codec_has_dict, 0))?;
320
- codec_class.define_method("compress", method!(block_codec_compress, 1))?;
321
- codec_class.define_method("_decompress", method!(block_codec_decompress, 2))?;
322
-
323
- let trainer_class = module.define_class("DictTrainer", ruby.class_object())?;
324
- trainer_class.define_singleton_method("_native_new", function!(dict_trainer_new, 1))?;
325
- trainer_class.define_method("add_sample", method!(dict_trainer_add_sample, 1))?;
326
- trainer_class.define_method("sample_count", method!(dict_trainer_sample_count, 0))?;
327
- trainer_class.define_method("total_bytes", method!(dict_trainer_total_bytes, 0))?;
328
- trainer_class.define_method("train", method!(dict_trainer_train, 0))?;
329
- trainer_class.define_method("max_dict_size", method!(dict_trainer_max_dict_size, 0))?;
330
- trainer_class.define_method("trained?", method!(dict_trainer_trained, 0))?;
331
-
332
- let frame_codec_class = module.define_class("FrameCodec", ruby.class_object())?;
333
- frame_codec_class
334
- .define_singleton_method("_native_new", function!(frame_codec_initialize, 2))?;
335
- frame_codec_class.define_method("compress", method!(frame_codec_compress, 1))?;
336
- frame_codec_class.define_method("decompress", method!(frame_codec_decompress, 1))?;
337
- frame_codec_class.define_method("size", method!(frame_codec_size, 0))?;
338
- frame_codec_class.define_method("has_dict?", method!(frame_codec_has_dict, 0))?;
339
- frame_codec_class.define_method("id", method!(frame_codec_id, 0))?;
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
  }