lz4rip 0.1.2 → 0.2.1

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